mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
test: expand interface settings and account editor coverage
Added coverage for version check button states, language selector, fit-expanded-images toggle, and account editor warning gate, continue flow, save/reset, and JSON validation.
This commit is contained in:
+142
-3
@@ -3,21 +3,33 @@ import { createElement } from 'react';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import packageJson from '../../../../../package.json';
|
||||
import InterfaceSettings from '../interface-settings';
|
||||
import useFeedViewSettingsStore from '../../../../stores/use-feed-view-settings-store';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
alertMock: vi.fn(),
|
||||
changeLanguageMock: vi.fn(),
|
||||
fetchMock: vi.fn(),
|
||||
fitExpandedImagesToScreen: false,
|
||||
setFitExpandedImagesToScreenMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
i18n: { changeLanguage: vi.fn(), language: 'en' },
|
||||
t: (key: string, opts?: Record<string, unknown>) => (opts ? `${key}:${JSON.stringify(opts)}` : key),
|
||||
i18n: { changeLanguage: testState.changeLanguageMock, language: 'en' },
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../../../stores/use-expanded-media-store', () => ({
|
||||
default: () => ({ fitExpandedImagesToScreen: false, setFitExpandedImagesToScreen: vi.fn() }),
|
||||
default: () => ({
|
||||
fitExpandedImagesToScreen: testState.fitExpandedImagesToScreen,
|
||||
setFitExpandedImagesToScreen: testState.setFitExpandedImagesToScreenMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../../version', () => ({
|
||||
@@ -35,6 +47,20 @@ const STORAGE_KEY = 'feed-view-settings-store';
|
||||
let root: Root;
|
||||
let container: HTMLDivElement;
|
||||
|
||||
const createFetchResponse = (body: unknown) => ({
|
||||
json: vi.fn().mockResolvedValue(body),
|
||||
});
|
||||
|
||||
const createDeferred = <T,>() => {
|
||||
let resolve!: (value: T) => void;
|
||||
let reject!: (reason?: unknown) => void;
|
||||
const promise = new Promise<T>((innerResolve, innerReject) => {
|
||||
resolve = innerResolve;
|
||||
reject = innerReject;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
};
|
||||
|
||||
const render = (children: React.ReactNode) => {
|
||||
act(() => {
|
||||
root.render(createElement(MemoryRouter, {}, children));
|
||||
@@ -47,7 +73,14 @@ describe('InterfaceSettings', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
testState.alertMock.mockReset();
|
||||
testState.changeLanguageMock.mockReset();
|
||||
testState.fetchMock.mockReset();
|
||||
testState.fitExpandedImagesToScreen = false;
|
||||
testState.setFitExpandedImagesToScreenMock.mockReset();
|
||||
useFeedViewSettingsStore.getState().setEnableInfiniteScroll(false);
|
||||
vi.stubGlobal('alert', testState.alertMock);
|
||||
vi.stubGlobal('fetch', testState.fetchMock);
|
||||
setItemSpy = vi.spyOn(Storage.prototype, 'setItem');
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -58,6 +91,7 @@ describe('InterfaceSettings', () => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
setItemSpy.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('renders enable_infinite_scroll_tip under the infinite scroll checkbox', () => {
|
||||
@@ -92,4 +126,109 @@ describe('InterfaceSettings', () => {
|
||||
expect(container.querySelector('[data-testid="board-mode"]')?.textContent).toBe('infinite');
|
||||
expect(setItemSpy).toHaveBeenCalledWith(STORAGE_KEY, expect.stringContaining('"enableInfiniteScroll":true'));
|
||||
});
|
||||
|
||||
it('toggles fit expanded images through the media store', async () => {
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const label = Array.from(container.querySelectorAll('label')).find((candidate) => candidate.textContent?.toLowerCase().includes('fit_expanded_images_to_screen'));
|
||||
const checkbox = label?.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
||||
expect(checkbox).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
checkbox?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
expect(testState.setFitExpandedImagesToScreenMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('changes the interface language from the language selector', async () => {
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const select = container.querySelector<HTMLSelectElement>('select');
|
||||
expect(select).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
if (select) {
|
||||
select.value = 'fr';
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
});
|
||||
|
||||
expect(testState.changeLanguageMock).toHaveBeenCalledWith('fr');
|
||||
});
|
||||
|
||||
it('disables the update button while fetching and restores it afterward', async () => {
|
||||
const pendingFetch = createDeferred<ReturnType<typeof createFetchResponse>>();
|
||||
testState.fetchMock.mockReturnValueOnce(pendingFetch.promise);
|
||||
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'check');
|
||||
expect(button).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
expect(button?.disabled).toBe(true);
|
||||
|
||||
pendingFetch.resolve(createFetchResponse({ version: packageJson.version }));
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(button?.disabled).toBe(false);
|
||||
expect(testState.alertMock).toHaveBeenCalledWith(expect.stringContaining('latest_stable_version'));
|
||||
});
|
||||
|
||||
it('alerts when a newer stable version is available', async () => {
|
||||
testState.fetchMock.mockResolvedValueOnce(createFetchResponse({ version: '9.9.9' }));
|
||||
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'check');
|
||||
expect(button).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const message = String(testState.alertMock.mock.calls.at(-1)?.[0] ?? '');
|
||||
expect(message).toContain('new_stable_version');
|
||||
expect(message).toContain('refresh_to_update');
|
||||
});
|
||||
|
||||
it('alerts when already on the latest stable version', async () => {
|
||||
testState.fetchMock.mockResolvedValueOnce(createFetchResponse({ version: packageJson.version }));
|
||||
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'check');
|
||||
expect(button).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(testState.alertMock).toHaveBeenCalledWith(expect.stringContaining('latest_stable_version'));
|
||||
});
|
||||
|
||||
it('alerts when fetching the latest version info fails', async () => {
|
||||
testState.fetchMock.mockRejectedValueOnce(new Error('network down'));
|
||||
|
||||
render(createElement(InterfaceSettings));
|
||||
|
||||
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === 'check');
|
||||
expect(button).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(testState.alertMock).toHaveBeenCalledWith('Failed to fetch latest version info: Error: network down');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user