test(webui): share shell bridge test helper
- add a reusable shell bridge factory for legacy shell-backed tests - trim route and bridge fixtures down to only the overrides they need
This commit is contained in:
parent
fec66e4de8
commit
d0245e3e16
5 changed files with 53 additions and 117 deletions
|
|
@ -4,31 +4,26 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SHELL_PROFILE_CONTEXT_CHANGED_EVENT,
|
SHELL_PROFILE_CONTEXT_CHANGED_EVENT,
|
||||||
type ShellBridge,
|
|
||||||
type ShellProfileContext,
|
type ShellProfileContext,
|
||||||
type ShellPageId,
|
type ShellPageId,
|
||||||
} from '@/platform/shell/bridge';
|
} from '@/platform/shell/bridge';
|
||||||
|
import { HttpResponse, http, server } from '@/test/msw';
|
||||||
|
import { createShellBridge } from '@/test/shell-bridge';
|
||||||
|
|
||||||
import { createAppQueryClient } from './query-client';
|
import { createAppQueryClient } from './query-client';
|
||||||
import { AppRouterProvider, createAppRouter } from './router';
|
import { AppRouterProvider, createAppRouter } from './router';
|
||||||
|
|
||||||
function mockIssuesFetch() {
|
describe('createAppRouter', () => {
|
||||||
return vi.fn(async (input: RequestInfo | URL) => {
|
beforeEach(() => {
|
||||||
const url = input instanceof Request ? input.url : String(input);
|
server.use(
|
||||||
|
http.get('/api/issues/counts', () =>
|
||||||
if (url.includes('/api/issues/counts')) {
|
HttpResponse.json({
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
success: true,
|
success: true,
|
||||||
counts: { open: 2, in_progress: 1, resolved: 0, dismissed: 0, total: 3 },
|
counts: { open: 2, in_progress: 1, resolved: 0, dismissed: 0, total: 3 },
|
||||||
}),
|
}),
|
||||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
),
|
||||||
);
|
http.get('/api/issues', () =>
|
||||||
}
|
HttpResponse.json({
|
||||||
|
|
||||||
if (url.includes('/api/issues?')) {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
success: true,
|
success: true,
|
||||||
total: 1,
|
total: 1,
|
||||||
issues: [
|
issues: [
|
||||||
|
|
@ -44,37 +39,8 @@ function mockIssuesFetch() {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unexpected fetch request: ${url}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge {
|
|
||||||
return {
|
|
||||||
getCurrentProfileContext: vi.fn(() => ({ profileId: 1, isAdmin: false })),
|
|
||||||
isPageAllowed: vi.fn(() => true),
|
|
||||||
getProfileHomePage: vi.fn<() => ShellPageId>(() => 'discover'),
|
|
||||||
resolveLegacyPath: vi.fn<(pathname: string) => ShellPageId | null>(() => 'search'),
|
|
||||||
setActivePageChrome: vi.fn(),
|
|
||||||
activateLegacyPath: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
cancelSimilarArtistsLoad: vi.fn(),
|
|
||||||
showReactHost: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
playLibraryTrack: vi.fn(),
|
|
||||||
startStream: vi.fn(),
|
|
||||||
showLoadingOverlay: vi.fn(),
|
|
||||||
hideLoadingOverlay: vi.fn(),
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createAppRouter', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.stubGlobal('fetch', mockIssuesFetch());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createShellBridge } from '@/test/shell-bridge';
|
||||||
|
|
||||||
import type { ShellProfileContext } from './bridge';
|
import type { ShellProfileContext } from './bridge';
|
||||||
|
|
||||||
import { SHELL_PROFILE_CONTEXT_CHANGED_EVENT, bindWindowWebRouter, waitForShellContext } from './bridge';
|
import {
|
||||||
|
SHELL_PROFILE_CONTEXT_CHANGED_EVENT,
|
||||||
|
bindWindowWebRouter,
|
||||||
|
waitForShellContext,
|
||||||
|
} from './bridge';
|
||||||
|
|
||||||
describe('waitForShellContext', () => {
|
describe('waitForShellContext', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -10,21 +16,7 @@ describe('waitForShellContext', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves immediately when the shell already has a profile', async () => {
|
it('resolves immediately when the shell already has a profile', async () => {
|
||||||
window.SoulSyncWebShellBridge = {
|
window.SoulSyncWebShellBridge = createShellBridge();
|
||||||
getProfileHomePage: vi.fn(() => 'discover'),
|
|
||||||
isPageAllowed: vi.fn(() => true),
|
|
||||||
activateLegacyPath: vi.fn(),
|
|
||||||
getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: true })),
|
|
||||||
resolveLegacyPath: vi.fn(() => 'issues'),
|
|
||||||
setActivePageChrome: vi.fn(),
|
|
||||||
cancelSimilarArtistsLoad: vi.fn(),
|
|
||||||
showReactHost: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
playLibraryTrack: vi.fn(),
|
|
||||||
startStream: vi.fn(),
|
|
||||||
showLoadingOverlay: vi.fn(),
|
|
||||||
hideLoadingOverlay: vi.fn(),
|
|
||||||
} as NonNullable<typeof window.SoulSyncWebShellBridge>;
|
|
||||||
|
|
||||||
await expect(waitForShellContext()).resolves.toEqual({
|
await expect(waitForShellContext()).resolves.toEqual({
|
||||||
bridge: window.SoulSyncWebShellBridge,
|
bridge: window.SoulSyncWebShellBridge,
|
||||||
|
|
@ -37,21 +29,9 @@ describe('waitForShellContext', () => {
|
||||||
|
|
||||||
it('waits for the legacy shell to publish profile context', async () => {
|
it('waits for the legacy shell to publish profile context', async () => {
|
||||||
const getCurrentProfileContext = vi.fn<() => ShellProfileContext | null>(() => null);
|
const getCurrentProfileContext = vi.fn<() => ShellProfileContext | null>(() => null);
|
||||||
window.SoulSyncWebShellBridge = {
|
window.SoulSyncWebShellBridge = createShellBridge({
|
||||||
getProfileHomePage: vi.fn(() => 'discover'),
|
|
||||||
isPageAllowed: vi.fn(() => true),
|
|
||||||
activateLegacyPath: vi.fn(),
|
|
||||||
getCurrentProfileContext,
|
getCurrentProfileContext,
|
||||||
resolveLegacyPath: vi.fn(() => 'issues'),
|
});
|
||||||
setActivePageChrome: vi.fn(),
|
|
||||||
cancelSimilarArtistsLoad: vi.fn(),
|
|
||||||
showReactHost: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
playLibraryTrack: vi.fn(),
|
|
||||||
startStream: vi.fn(),
|
|
||||||
showLoadingOverlay: vi.fn(),
|
|
||||||
hideLoadingOverlay: vi.fn(),
|
|
||||||
} as NonNullable<typeof window.SoulSyncWebShellBridge>;
|
|
||||||
|
|
||||||
const contextPromise = waitForShellContext();
|
const contextPromise = waitForShellContext();
|
||||||
|
|
||||||
|
|
@ -106,7 +86,9 @@ describe('bindWindowWebRouter', () => {
|
||||||
|
|
||||||
bindWindowWebRouter({ navigate } as never);
|
bindWindowWebRouter({ navigate } as never);
|
||||||
|
|
||||||
await expect(window.SoulSyncWebRouter?.navigateToPage('artist-detail', {} as never)).resolves.toBe(false);
|
await expect(
|
||||||
|
window.SoulSyncWebRouter?.navigateToPage('artist-detail', {} as never),
|
||||||
|
).resolves.toBe(false);
|
||||||
expect(navigate).not.toHaveBeenCalled();
|
expect(navigate).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,9 @@ import { createMemoryHistory } from '@tanstack/react-router';
|
||||||
import { render, waitFor } from '@testing-library/react';
|
import { render, waitFor } from '@testing-library/react';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import type { ShellBridge, ShellPageId } from '@/platform/shell/bridge';
|
|
||||||
|
|
||||||
import { createAppQueryClient } from '@/app/query-client';
|
import { createAppQueryClient } from '@/app/query-client';
|
||||||
import { AppRouterProvider, createAppRouter } from '@/app/router';
|
import { AppRouterProvider, createAppRouter } from '@/app/router';
|
||||||
|
import { createShellBridge } from '@/test/shell-bridge';
|
||||||
function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge {
|
|
||||||
return {
|
|
||||||
getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: false })),
|
|
||||||
isPageAllowed: vi.fn(() => true),
|
|
||||||
getProfileHomePage: vi.fn<() => ShellPageId>(() => 'discover'),
|
|
||||||
resolveLegacyPath: vi.fn<(pathname: string) => ShellPageId | null>(() => 'artist-detail'),
|
|
||||||
setActivePageChrome: vi.fn(),
|
|
||||||
activateLegacyPath: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
cancelSimilarArtistsLoad: vi.fn(),
|
|
||||||
showReactHost: vi.fn(),
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderArtistDetailRoute(initialEntries = ['/artist-detail/library/42']) {
|
function renderArtistDetailRoute(initialEntries = ['/artist-detail/library/42']) {
|
||||||
const queryClient = createAppQueryClient();
|
const queryClient = createAppQueryClient();
|
||||||
|
|
@ -87,7 +71,8 @@ describe('artist-detail route', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancelSimilarArtistsLoad = window.SoulSyncWebShellBridge?.cancelSimilarArtistsLoad as ReturnType<typeof vi.fn>;
|
const cancelSimilarArtistsLoad = window.SoulSyncWebShellBridge
|
||||||
|
?.cancelSimilarArtistsLoad as ReturnType<typeof vi.fn>;
|
||||||
cancelSimilarArtistsLoad.mockClear();
|
cancelSimilarArtistsLoad.mockClear();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ import { createMemoryHistory } from '@tanstack/react-router';
|
||||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import type { ShellBridge, ShellPageId } from '@/platform/shell/bridge';
|
|
||||||
|
|
||||||
import { createAppQueryClient } from '@/app/query-client';
|
import { createAppQueryClient } from '@/app/query-client';
|
||||||
import { AppRouterProvider, createAppRouter } from '@/app/router';
|
import { AppRouterProvider, createAppRouter } from '@/app/router';
|
||||||
|
import { createShellBridge } from '@/test/shell-bridge';
|
||||||
|
|
||||||
function createResponse(body: unknown, ok = true, status = 200) {
|
function createResponse(body: unknown, ok = true, status = 200) {
|
||||||
return new Response(JSON.stringify(body), {
|
return new Response(JSON.stringify(body), {
|
||||||
|
|
@ -14,26 +13,6 @@ function createResponse(body: unknown, ok = true, status = 200) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge {
|
|
||||||
return {
|
|
||||||
getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: true })),
|
|
||||||
isPageAllowed: vi.fn(() => true),
|
|
||||||
getProfileHomePage: vi.fn<() => ShellPageId>(() => 'discover'),
|
|
||||||
resolveLegacyPath: vi.fn<(pathname: string) => ShellPageId | null>(() => 'search'),
|
|
||||||
setActivePageChrome: vi.fn(),
|
|
||||||
activateLegacyPath: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
cancelSimilarArtistsLoad: vi.fn(),
|
|
||||||
showReactHost: vi.fn(),
|
|
||||||
navigateToArtistDetail: vi.fn(),
|
|
||||||
playLibraryTrack: vi.fn(),
|
|
||||||
startStream: vi.fn(),
|
|
||||||
showLoadingOverlay: vi.fn(),
|
|
||||||
hideLoadingOverlay: vi.fn(),
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderIssuesRoute(initialEntries = ['/issues']) {
|
function renderIssuesRoute(initialEntries = ['/issues']) {
|
||||||
const queryClient = createAppQueryClient();
|
const queryClient = createAppQueryClient();
|
||||||
const history = createMemoryHistory({ initialEntries });
|
const history = createMemoryHistory({ initialEntries });
|
||||||
|
|
|
||||||
24
webui/src/test/shell-bridge.ts
Normal file
24
webui/src/test/shell-bridge.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
import type { ShellBridge, ShellPageId } from '@/platform/shell/bridge';
|
||||||
|
|
||||||
|
export function createShellBridge(overrides: Partial<ShellBridge> = {}): ShellBridge {
|
||||||
|
const bridge: ShellBridge = {
|
||||||
|
getCurrentProfileContext: vi.fn(() => ({ profileId: 2, isAdmin: true })),
|
||||||
|
isPageAllowed: vi.fn(() => true),
|
||||||
|
getProfileHomePage: vi.fn<() => ShellPageId>(() => 'discover'),
|
||||||
|
resolveLegacyPath: vi.fn<(pathname: string) => ShellPageId | null>(() => 'search'),
|
||||||
|
setActivePageChrome: vi.fn(),
|
||||||
|
activateLegacyPath: vi.fn(),
|
||||||
|
cancelSimilarArtistsLoad: vi.fn(),
|
||||||
|
navigateToArtistDetail: vi.fn(),
|
||||||
|
playLibraryTrack: vi.fn(),
|
||||||
|
showReactHost: vi.fn(),
|
||||||
|
startStream: vi.fn(),
|
||||||
|
showLoadingOverlay: vi.fn(),
|
||||||
|
hideLoadingOverlay: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(bridge, overrides);
|
||||||
|
return bridge;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue