import * as React from 'react'; import { createElement } from 'react'; import { createRoot, Root } from 'react-dom/client'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import MediaHostingSettings from '../media-hosting-settings'; import { MEDIA_HOSTING_PROVIDERS } from '../../../../stores/use-media-hosting-store'; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const act = (React as { act?: (cb: () => void | Promise) => void | Promise }).act as (cb: () => void | Promise) => void | Promise; vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); const mockGetPlatform = vi.fn(() => 'web'); vi.mock('@capacitor/core', () => ({ Capacitor: { getPlatform: () => mockGetPlatform() }, })); const providerAvailabilityRef = vi.hoisted(() => ({ value: {} as Record, })); vi.mock('../../../../hooks/use-provider-availability', () => ({ useProviderAvailability: () => providerAvailabilityRef.value, })); const mockSetUploadMode = vi.fn(); const mockSetPreferredProvider = vi.fn(); const uploadModeRef = vi.hoisted(() => ({ value: 'random' as 'random' | 'preferred' | 'none' })); const preferredProviderRef = vi.hoisted(() => ({ value: 'catbox' as 'catbox' | 'imgur' | 'imgbb', })); vi.mock('../../../../stores/use-media-hosting-store', async (importOriginal) => { const mod = await importOriginal(); return { ...mod, default: (selector: (state: Record) => unknown) => selector({ uploadMode: uploadModeRef.value, preferredProvider: preferredProviderRef.value, setUploadMode: mockSetUploadMode, setPreferredProvider: mockSetPreferredProvider, }), }; }); let root: Root; let container: HTMLDivElement; const render = () => { act(() => { root.render(createElement(MediaHostingSettings)); }); }; describe('MediaHostingSettings', () => { beforeEach(() => { vi.clearAllMocks(); uploadModeRef.value = 'random'; preferredProviderRef.value = 'catbox'; providerAvailabilityRef.value = {}; mockGetPlatform.mockReturnValue('web'); (window as unknown as { electronApi?: unknown }).electronApi = undefined; container = document.createElement('div'); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { act(() => root.unmount()); container.remove(); }); it('renders mode options: Random, Preferred, None', () => { render(); const radios = container.querySelectorAll('input[name="media-hosting-provider"]'); expect(radios.length).toBe(3); expect(container.textContent).toContain('media_hosting_random'); expect(container.textContent).toContain('media_hosting_preferred'); expect(container.textContent).toContain('media_hosting_none'); }); it('shows provider list when Preferred is selected', () => { uploadModeRef.value = 'preferred'; render(); for (const provider of MEDIA_HOSTING_PROVIDERS) { expect(container.textContent).toContain(provider.label); expect(container.textContent).toContain(provider.homepageUrl); } }); it('default selected is random', () => { render(); const randomRadio = container.querySelector('input[value="random"]'); expect(randomRadio).not.toBeNull(); expect(randomRadio?.checked).toBe(true); }); it('clicking None updates store via setUploadMode', async () => { mockGetPlatform.mockReturnValue('android'); render(); const noneRadio = container.querySelector('input[value="none"]'); expect(noneRadio).not.toBeNull(); await act(async () => { noneRadio?.click(); }); expect(mockSetUploadMode).toHaveBeenCalledWith('none'); }); it('disables unsupported providers on Android', async () => { uploadModeRef.value = 'preferred'; preferredProviderRef.value = 'catbox'; mockGetPlatform.mockReturnValue('android'); render(); const imgurRadio = container.querySelector('input[value="imgur"]'); expect(imgurRadio).not.toBeNull(); expect(imgurRadio?.disabled).toBe(true); await act(async () => { imgurRadio?.click(); }); expect(mockSetPreferredProvider).not.toHaveBeenCalled(); }); it('clicking supported provider in preferred mode calls setPreferredProvider', async () => { uploadModeRef.value = 'preferred'; preferredProviderRef.value = 'imgur'; mockGetPlatform.mockReturnValue('android'); render(); const catboxRadio = container.querySelector('input[value="catbox"]'); expect(catboxRadio).not.toBeNull(); expect(catboxRadio?.disabled).toBe(false); await act(async () => { catboxRadio?.click(); }); expect(mockSetPreferredProvider).toHaveBeenCalledWith('catbox'); }); it('enables ImgBB on Android', async () => { uploadModeRef.value = 'preferred'; preferredProviderRef.value = 'catbox'; mockGetPlatform.mockReturnValue('android'); render(); const imgbbRadio = container.querySelector('input[value="imgbb"]'); expect(imgbbRadio).not.toBeNull(); expect(imgbbRadio?.disabled).toBe(false); await act(async () => { imgbbRadio?.click(); }); expect(mockSetPreferredProvider).toHaveBeenCalledWith('imgbb'); }); it('disables providers that are unavailable from this network', async () => { uploadModeRef.value = 'preferred'; preferredProviderRef.value = 'catbox'; providerAvailabilityRef.value = { imgbb: 'unavailable' }; mockGetPlatform.mockReturnValue('android'); render(); const imgbbRadio = container.querySelector('input[value="imgbb"]'); expect(imgbbRadio).not.toBeNull(); expect(imgbbRadio?.disabled).toBe(true); expect(imgbbRadio?.closest('label')?.title).toBe('media_hosting_provider_unavailable'); expect(container.textContent).toContain('media_hosting_provider_unavailable'); await act(async () => { imgbbRadio?.click(); }); expect(mockSetPreferredProvider).not.toHaveBeenCalled(); }); it('clicking Random updates store via setUploadMode', async () => { uploadModeRef.value = 'preferred'; mockGetPlatform.mockReturnValue('android'); render(); const randomRadio = container.querySelector('input[value="random"]'); expect(randomRadio).not.toBeNull(); await act(async () => { randomRadio?.click(); }); expect(mockSetUploadMode).toHaveBeenCalledWith('random'); }); it('clicking Preferred updates store via setUploadMode', async () => { mockGetPlatform.mockReturnValue('android'); render(); const preferredRadio = container.querySelector('input[value="preferred"]'); expect(preferredRadio).not.toBeNull(); await act(async () => { preferredRadio?.click(); }); expect(mockSetUploadMode).toHaveBeenCalledWith('preferred'); }); it('on web runtime, warning renders above mode radios and all radios are disabled', () => { uploadModeRef.value = 'preferred'; mockGetPlatform.mockReturnValue('web'); (window as unknown as { electronApi?: unknown }).electronApi = undefined; render(); const warningDiv = Array.from(container.querySelectorAll('div')).find((d) => d.textContent?.includes('upload_not_supported_web_before_link')); expect(warningDiv).toBeTruthy(); const firstModeRadio = container.querySelector('input[name="media-hosting-provider"]'); expect(firstModeRadio).toBeTruthy(); expect(warningDiv!.compareDocumentPosition(firstModeRadio!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); const modeRadios = container.querySelectorAll('input[name="media-hosting-provider"]'); expect(modeRadios.length).toBe(3); for (const radio of modeRadios) { expect(radio.disabled).toBe(true); } const providerRadios = container.querySelectorAll('input[name="media-hosting-provider-provider"]'); expect(providerRadios.length).toBe(MEDIA_HOSTING_PROVIDERS.length); for (const radio of providerRadios) { expect(radio.disabled).toBe(true); } expect(container.textContent).toContain('upload_not_supported_web_before_link'); expect(container.textContent).toContain('upload_not_supported_web_link_text'); }); it('on web runtime, no Catbox-only web hint key is referenced', () => { mockGetPlatform.mockReturnValue('web'); (window as unknown as { electronApi?: unknown }).electronApi = undefined; render(); expect(container.textContent).not.toContain('media_hosting_provider_disabled_on_web'); }); it('on electron runtime, all radios are enabled and no CORS warning is shown', () => { uploadModeRef.value = 'preferred'; mockGetPlatform.mockReturnValue('web'); (window as unknown as { electronApi?: unknown }).electronApi = { isElectron: true }; render(); const modeRadios = container.querySelectorAll('input[name="media-hosting-provider"]'); expect(modeRadios.length).toBe(3); for (const radio of modeRadios) { expect(radio.disabled).toBe(false); } const providerRadios = container.querySelectorAll('input[name="media-hosting-provider-provider"]'); expect(providerRadios.length).toBe(MEDIA_HOSTING_PROVIDERS.length); for (const radio of providerRadios) { expect(radio.disabled).toBe(false); } expect(container.textContent).not.toContain('upload_not_supported_web_before_link'); }); });