diff --git a/app/test/OAuthDiscord.test.tsx b/app/test/OAuthDiscord.test.tsx index 1eb53d48a..ce6a7286c 100644 --- a/app/test/OAuthDiscord.test.tsx +++ b/app/test/OAuthDiscord.test.tsx @@ -9,13 +9,13 @@ * - Error handling when backend URL lookup fails * - dev-mode URL construction (?responseType=json) */ -import type { ComponentProps } from 'react'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; +import type { ComponentProps } from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { renderWithProviders } from '../src/test/test-utils'; import OAuthProviderButton from '../src/components/oauth/OAuthProviderButton'; import { oauthProviderConfigs } from '../src/components/oauth/providerConfigs'; +import { renderWithProviders } from '../src/test/test-utils'; // --------------------------------------------------------------------------- // Module mocks @@ -29,7 +29,7 @@ const { mockGetBackendUrl, mockOpenUrl, mockIsTauri } = vi.hoisted(() => ({ vi.mock('../src/services/backendUrl', () => ({ getBackendUrl: mockGetBackendUrl })); vi.mock('../src/utils/openUrl', () => ({ openUrl: mockOpenUrl })); -vi.mock('../src/utils/tauriCommands', async (importOriginal) => { +vi.mock('../src/utils/tauriCommands', async importOriginal => { const actual = await importOriginal>(); return { ...actual, isTauri: mockIsTauri }; }); @@ -43,7 +43,10 @@ const discordConfig = oauthProviderConfigs.find(p => p.id === 'discord')!; const renderDiscordButton = (props: Partial> = {}) => renderWithProviders(); -const clickButton = (btn: HTMLElement) => act(async () => { fireEvent.click(btn); }); +const clickButton = (btn: HTMLElement) => + act(async () => { + fireEvent.click(btn); + }); // --------------------------------------------------------------------------- // Rendering @@ -115,9 +118,7 @@ describe('OAuthProviderButton (Discord) — web OAuth flow', () => { renderDiscordButton(); await clickButton(screen.getByRole('button', { name: /discord/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockOpenUrl).not.toHaveBeenCalled(); }); @@ -125,9 +126,7 @@ describe('OAuthProviderButton (Discord) — web OAuth flow', () => { renderDiscordButton(); await clickButton(screen.getByRole('button', { name: /discord/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); }); }); @@ -180,7 +179,11 @@ describe('OAuthProviderButton (Discord) — Tauri OAuth flow', () => { describe('OAuthProviderButton (Discord) — loading state', () => { it('shows spinner and "Connecting..." while getBackendUrl is pending', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderDiscordButton(); @@ -191,12 +194,18 @@ describe('OAuthProviderButton (Discord) — loading state', () => { expect(document.querySelector('.animate-spin')).toBeInTheDocument(); expect(button).toBeDisabled(); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); it('ignores a second click while already loading', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderDiscordButton(); @@ -208,7 +217,9 @@ describe('OAuthProviderButton (Discord) — loading state', () => { fireEvent.click(button); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); }); @@ -242,9 +253,7 @@ describe('OAuthProviderButton (Discord) — error handling', () => { renderDiscordButton(); await clickButton(screen.getByRole('button', { name: /discord/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /discord/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /discord/i })).toBeEnabled()); expect((window.location as unknown as { href: string }).href).toBe(''); (window as unknown as Record).location = originalLocation; @@ -257,9 +266,7 @@ describe('OAuthProviderButton (Discord) — error handling', () => { renderDiscordButton(); await clickButton(screen.getByRole('button', { name: /discord/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /discord/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /discord/i })).toBeEnabled()); expect(mockOpenUrl).not.toHaveBeenCalled(); }); diff --git a/app/test/OAuthGitHub.test.tsx b/app/test/OAuthGitHub.test.tsx index be7851842..36686529a 100644 --- a/app/test/OAuthGitHub.test.tsx +++ b/app/test/OAuthGitHub.test.tsx @@ -9,13 +9,13 @@ * - Error handling when backend URL lookup fails * - dev-mode URL construction (?responseType=json) */ -import type { ComponentProps } from 'react'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; +import type { ComponentProps } from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { renderWithProviders } from '../src/test/test-utils'; import OAuthProviderButton from '../src/components/oauth/OAuthProviderButton'; import { oauthProviderConfigs } from '../src/components/oauth/providerConfigs'; +import { renderWithProviders } from '../src/test/test-utils'; // --------------------------------------------------------------------------- // Module mocks @@ -29,7 +29,7 @@ const { mockGetBackendUrl, mockOpenUrl, mockIsTauri } = vi.hoisted(() => ({ vi.mock('../src/services/backendUrl', () => ({ getBackendUrl: mockGetBackendUrl })); vi.mock('../src/utils/openUrl', () => ({ openUrl: mockOpenUrl })); -vi.mock('../src/utils/tauriCommands', async (importOriginal) => { +vi.mock('../src/utils/tauriCommands', async importOriginal => { const actual = await importOriginal>(); return { ...actual, isTauri: mockIsTauri }; }); @@ -43,7 +43,10 @@ const githubConfig = oauthProviderConfigs.find(p => p.id === 'github')!; const renderGitHubButton = (props: Partial> = {}) => renderWithProviders(); -const clickButton = (btn: HTMLElement) => act(async () => { fireEvent.click(btn); }); +const clickButton = (btn: HTMLElement) => + act(async () => { + fireEvent.click(btn); + }); // --------------------------------------------------------------------------- // Rendering @@ -115,9 +118,7 @@ describe('OAuthProviderButton (GitHub) — web OAuth flow', () => { renderGitHubButton(); await clickButton(screen.getByRole('button', { name: /github/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockOpenUrl).not.toHaveBeenCalled(); }); @@ -125,9 +126,7 @@ describe('OAuthProviderButton (GitHub) — web OAuth flow', () => { renderGitHubButton(); await clickButton(screen.getByRole('button', { name: /github/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); }); }); @@ -180,7 +179,11 @@ describe('OAuthProviderButton (GitHub) — Tauri OAuth flow', () => { describe('OAuthProviderButton (GitHub) — loading state', () => { it('shows spinner and "Connecting..." while getBackendUrl is pending', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderGitHubButton(); @@ -191,12 +194,18 @@ describe('OAuthProviderButton (GitHub) — loading state', () => { expect(document.querySelector('.animate-spin')).toBeInTheDocument(); expect(button).toBeDisabled(); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); it('ignores a second click while already loading', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderGitHubButton(); @@ -208,7 +217,9 @@ describe('OAuthProviderButton (GitHub) — loading state', () => { fireEvent.click(button); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); }); @@ -242,9 +253,7 @@ describe('OAuthProviderButton (GitHub) — error handling', () => { renderGitHubButton(); await clickButton(screen.getByRole('button', { name: /github/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /github/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /github/i })).toBeEnabled()); expect((window.location as unknown as { href: string }).href).toBe(''); (window as unknown as Record).location = originalLocation; @@ -257,9 +266,7 @@ describe('OAuthProviderButton (GitHub) — error handling', () => { renderGitHubButton(); await clickButton(screen.getByRole('button', { name: /github/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /github/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /github/i })).toBeEnabled()); expect(mockOpenUrl).not.toHaveBeenCalled(); }); diff --git a/app/test/OAuthLoginSection.test.tsx b/app/test/OAuthLoginSection.test.tsx index 0f7e5b53b..0e974199c 100644 --- a/app/test/OAuthLoginSection.test.tsx +++ b/app/test/OAuthLoginSection.test.tsx @@ -9,14 +9,14 @@ * - Error handling when the backend URL lookup fails * - dev-mode URL construction (responseType=json query param) */ -import type { ComponentProps } from 'react'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; +import type { ComponentProps } from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { renderWithProviders } from '../src/test/test-utils'; import OAuthLoginSection from '../src/components/oauth/OAuthLoginSection'; import OAuthProviderButton from '../src/components/oauth/OAuthProviderButton'; import { oauthProviderConfigs } from '../src/components/oauth/providerConfigs'; +import { renderWithProviders } from '../src/test/test-utils'; // --------------------------------------------------------------------------- // Module mocks @@ -30,15 +30,11 @@ const { mockGetBackendUrl, mockOpenUrl, mockIsTauri } = vi.hoisted(() => ({ mockIsTauri: vi.fn(), })); -vi.mock('../src/services/backendUrl', () => ({ - getBackendUrl: mockGetBackendUrl, -})); +vi.mock('../src/services/backendUrl', () => ({ getBackendUrl: mockGetBackendUrl })); -vi.mock('../src/utils/openUrl', () => ({ - openUrl: mockOpenUrl, -})); +vi.mock('../src/utils/openUrl', () => ({ openUrl: mockOpenUrl })); -vi.mock('../src/utils/tauriCommands', async (importOriginal) => { +vi.mock('../src/utils/tauriCommands', async importOriginal => { const actual = await importOriginal>(); return { ...actual, isTauri: mockIsTauri }; }); @@ -58,7 +54,10 @@ const renderGoogleButton = (props: Partial); // act() with an async callback returns Promise, making await valid. -const clickButton = (btn: HTMLElement) => act(async () => { fireEvent.click(btn); }); +const clickButton = (btn: HTMLElement) => + act(async () => { + fireEvent.click(btn); + }); // --------------------------------------------------------------------------- // OAuthLoginSection — rendering @@ -170,9 +169,7 @@ describe('OAuthProviderButton (Google) — web OAuth flow', () => { renderGoogleButton(); await clickButton(screen.getByRole('button', { name: /google/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockOpenUrl).not.toHaveBeenCalled(); }); @@ -180,9 +177,7 @@ describe('OAuthProviderButton (Google) — web OAuth flow', () => { renderGoogleButton(); await clickButton(screen.getByRole('button', { name: /google/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); }); }); @@ -227,7 +222,9 @@ describe('OAuthProviderButton (Google) — loading state', () => { it('shows spinner and "Connecting..." text while login is in progress', async () => { let resolveBackendUrl!: (_v: string) => void; mockGetBackendUrl.mockReturnValue( - new Promise(res => { resolveBackendUrl = res; }) + new Promise(res => { + resolveBackendUrl = res; + }) ); mockIsTauri.mockReturnValue(false); @@ -241,13 +238,17 @@ describe('OAuthProviderButton (Google) — loading state', () => { expect(button).toBeDisabled(); // Settle the promise so React doesn't warn about state updates after unmount - await act(async () => { resolveBackendUrl('http://localhost:5005'); }); + await act(async () => { + resolveBackendUrl('http://localhost:5005'); + }); }); it('does not respond to a second click while already loading', async () => { let resolveBackendUrl!: (_v: string) => void; mockGetBackendUrl.mockReturnValue( - new Promise(res => { resolveBackendUrl = res; }) + new Promise(res => { + resolveBackendUrl = res; + }) ); mockIsTauri.mockReturnValue(false); @@ -261,7 +262,9 @@ describe('OAuthProviderButton (Google) — loading state', () => { fireEvent.click(button); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); - await act(async () => { resolveBackendUrl('http://localhost:5005'); }); + await act(async () => { + resolveBackendUrl('http://localhost:5005'); + }); }); it('remains in loading state after successful Tauri openUrl (awaits deep-link callback)', async () => { @@ -311,9 +314,7 @@ describe('OAuthProviderButton (Google) — error handling', () => { renderGoogleButton(); await clickButton(screen.getByRole('button', { name: /google/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /google/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /google/i })).toBeEnabled()); expect((window.location as unknown as { href: string }).href).toBe(''); (window as unknown as Record).location = originalLocation; @@ -326,9 +327,7 @@ describe('OAuthProviderButton (Google) — error handling', () => { renderGoogleButton(); await clickButton(screen.getByRole('button', { name: /google/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /google/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /google/i })).toBeEnabled()); expect(mockOpenUrl).not.toHaveBeenCalled(); }); @@ -371,9 +370,7 @@ describe('OAuthProviderButton (Google) — dev mode URL params', () => { renderGoogleButton(); await clickButton(screen.getByRole('button', { name: /google/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect((window.location as unknown as { href: string }).href).toBe( 'https://api.example.com/auth/google/login?responseType=json' ); diff --git a/app/test/OAuthTwitter.test.tsx b/app/test/OAuthTwitter.test.tsx index 649d85d10..fcd3c8c20 100644 --- a/app/test/OAuthTwitter.test.tsx +++ b/app/test/OAuthTwitter.test.tsx @@ -9,13 +9,13 @@ * - Error handling when backend URL lookup fails * - dev-mode URL construction (?responseType=json) */ -import type { ComponentProps } from 'react'; import { act, fireEvent, screen, waitFor } from '@testing-library/react'; +import type { ComponentProps } from 'react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { renderWithProviders } from '../src/test/test-utils'; import OAuthProviderButton from '../src/components/oauth/OAuthProviderButton'; import { oauthProviderConfigs } from '../src/components/oauth/providerConfigs'; +import { renderWithProviders } from '../src/test/test-utils'; // --------------------------------------------------------------------------- // Module mocks @@ -29,7 +29,7 @@ const { mockGetBackendUrl, mockOpenUrl, mockIsTauri } = vi.hoisted(() => ({ vi.mock('../src/services/backendUrl', () => ({ getBackendUrl: mockGetBackendUrl })); vi.mock('../src/utils/openUrl', () => ({ openUrl: mockOpenUrl })); -vi.mock('../src/utils/tauriCommands', async (importOriginal) => { +vi.mock('../src/utils/tauriCommands', async importOriginal => { const actual = await importOriginal>(); return { ...actual, isTauri: mockIsTauri }; }); @@ -43,7 +43,10 @@ const twitterConfig = oauthProviderConfigs.find(p => p.id === 'twitter')!; const renderTwitterButton = (props: Partial> = {}) => renderWithProviders(); -const clickButton = (btn: HTMLElement) => act(async () => { fireEvent.click(btn); }); +const clickButton = (btn: HTMLElement) => + act(async () => { + fireEvent.click(btn); + }); // --------------------------------------------------------------------------- // Rendering @@ -115,9 +118,7 @@ describe('OAuthProviderButton (Twitter) — web OAuth flow', () => { renderTwitterButton(); await clickButton(screen.getByRole('button', { name: /twitter/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockOpenUrl).not.toHaveBeenCalled(); }); @@ -125,9 +126,7 @@ describe('OAuthProviderButton (Twitter) — web OAuth flow', () => { renderTwitterButton(); await clickButton(screen.getByRole('button', { name: /twitter/i })); - await waitFor(() => - expect((window.location as unknown as { href: string }).href).not.toBe('') - ); + await waitFor(() => expect((window.location as unknown as { href: string }).href).not.toBe('')); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); }); }); @@ -180,7 +179,11 @@ describe('OAuthProviderButton (Twitter) — Tauri OAuth flow', () => { describe('OAuthProviderButton (Twitter) — loading state', () => { it('shows spinner and "Connecting..." while getBackendUrl is pending', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderTwitterButton(); @@ -191,12 +194,18 @@ describe('OAuthProviderButton (Twitter) — loading state', () => { expect(document.querySelector('.animate-spin')).toBeInTheDocument(); expect(button).toBeDisabled(); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); it('ignores a second click while already loading', async () => { let resolve!: (_v: string) => void; - mockGetBackendUrl.mockReturnValue(new Promise(res => { resolve = res; })); + mockGetBackendUrl.mockReturnValue( + new Promise(res => { + resolve = res; + }) + ); mockIsTauri.mockReturnValue(false); renderTwitterButton(); @@ -208,7 +217,9 @@ describe('OAuthProviderButton (Twitter) — loading state', () => { fireEvent.click(button); expect(mockGetBackendUrl).toHaveBeenCalledTimes(1); - await act(async () => { resolve('http://localhost:5005'); }); + await act(async () => { + resolve('http://localhost:5005'); + }); }); }); @@ -242,9 +253,7 @@ describe('OAuthProviderButton (Twitter) — error handling', () => { renderTwitterButton(); await clickButton(screen.getByRole('button', { name: /twitter/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /twitter/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /twitter/i })).toBeEnabled()); expect((window.location as unknown as { href: string }).href).toBe(''); (window as unknown as Record).location = originalLocation; @@ -257,9 +266,7 @@ describe('OAuthProviderButton (Twitter) — error handling', () => { renderTwitterButton(); await clickButton(screen.getByRole('button', { name: /twitter/i })); - await waitFor(() => - expect(screen.getByRole('button', { name: /twitter/i })).toBeEnabled() - ); + await waitFor(() => expect(screen.getByRole('button', { name: /twitter/i })).toBeEnabled()); expect(mockOpenUrl).not.toHaveBeenCalled(); }); diff --git a/app/test/tsconfig.unit.json b/app/test/tsconfig.unit.json index 326ae5d3e..455b8b212 100644 --- a/app/test/tsconfig.unit.json +++ b/app/test/tsconfig.unit.json @@ -1,7 +1,5 @@ { "extends": "../tsconfig.json", - "compilerOptions": { - "types": ["vitest/globals", "@testing-library/jest-dom", "node"] - }, + "compilerOptions": { "types": ["vitest/globals", "@testing-library/jest-dom", "node"] }, "include": ["../src", "./*.test.ts", "./*.test.tsx"] }