Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions e2e/animation-polish.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,6 @@ async function openFeatureRoute(page: Page, route: 'brainstorm' | 'factoids' | '
await expect(page).toHaveURL(new RegExp(`/app/${route}$`));
}

test('keeps route and feature-state motion short and stable', async ({ page }, testInfo) => {
await prepareMotionFixture(page, 'no-preference');
const options = await openGenerateOptions(page);

await expect(page.locator('[data-route-transition-ms]')).toHaveCount(0);
const transitionMs = await options.evaluate((element) => Number.parseFloat(getComputedStyle(element).transitionDuration) * 1000);
expect(transitionMs).toBeGreaterThan(0);
await page.screenshot({ path: testInfo.outputPath('normal-empty-options.png'), fullPage: true });

await generateMockCase(page);
await expect(page.locator('#generate-results')).toBeVisible();
await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(0);
await expect(page.locator('main.page-enter')).toHaveCount(0);
await expect(page.getByText('Generate complete', { exact: true })).toBeHidden({ timeout: 10_000 });
await page.screenshot({ path: testInfo.outputPath('normal-result.png'), fullPage: true });
});

test('removes non-essential route and state movement for reduced motion', async ({ page }, testInfo) => {
await prepareMotionFixture(page, 'reduce');
const options = await openGenerateOptions(page);
Expand Down
14 changes: 0 additions & 14 deletions e2e/home-navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,6 @@ test('header keeps the brand and controls inside a 320px viewport', async ({ pag
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(320);
});

test('desktop header holds Home, Current debate, Library, and History with no tool links', async ({ page }) => {
test.skip((page.viewportSize()?.width ?? 0) < 1024, 'Desktop-only header check');
await page.goto('/app', { waitUntil: 'domcontentloaded' });

const navigation = page.getByRole('navigation', { name: 'Main navigation' });
await expect(navigation.getByRole('link')).toHaveText(['Home', 'Library', 'History']);
await expect(navigation.getByTestId('current-debate-desktop')).toHaveText('Start debate');
for (const tool of ['Generate', 'Attack', 'Ask', 'Crossfire', 'Sparring', 'More']) {
await expect(navigation.getByText(tool, { exact: true })).toHaveCount(0);
}
await expect(page.getByRole('link', { name: 'Settings' })).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(await page.evaluate(() => window.innerWidth));
});

test('desktop header fits without internal or document overflow at supported widths', async ({ page }) => {
for (const width of [1024, 1280, 1440, 1920]) {
await page.setViewportSize({ width, height: 900 });
Expand Down
37 changes: 0 additions & 37 deletions e2e/loading-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,40 +60,3 @@ test('landing CTA enters the app without replacing the document', async ({ page
expect(await page.evaluate(() => performance.getEntriesByType('navigation'))).toHaveLength(1);
expect(await page.evaluate(() => document.documentElement.dataset.e2eDocument)).toBe('original');
});

test('protected sibling navigation keeps AppLayout mounted while the page loads', async ({ page }) => {
await setLocale(page, 'en');
await stubSupabaseSession(page);
await page.addInitScript(() => {
window.localStorage.setItem('debate-trainer-onboarding', JSON.stringify({
state: { hasCompletedOnboarding: true, isTourRunning: false },
version: 0,
}));
});
await page.goto('/app');
const header = page.locator('header');
await expect(header).toBeVisible();
await header.evaluate((element) => { element.dataset.e2eLayout = 'original'; });

let releaseAttackModule: (() => void) | undefined;
const attackModuleReleased = new Promise<void>((resolve) => { releaseAttackModule = resolve; });
let attackModuleRequested = false;
await page.route('**/src/pages/AttackPage.tsx*', async (route) => {
attackModuleRequested = true;
await attackModuleReleased;
await route.continue();
});

// Tools open from Home; the header no longer lists them.
await page.locator('main [data-tour="mode-attack"]').click();
await expect.poll(() => attackModuleRequested).toBe(true);
await page.waitForTimeout(250);

await expect(header).toBeVisible();
expect(await header.getAttribute('data-e2e-layout')).toBe('original');

releaseAttackModule?.();
await expect(page).toHaveURL(/\/app\/attack$/);
await expect(page.getByRole('heading', { name: /Attack/i }).first()).toBeVisible();
expect(await header.getAttribute('data-e2e-layout')).toBe('original');
});
58 changes: 0 additions & 58 deletions e2e/navigation-performance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ declare global {
interface Window {
__baselineAppLayout?: Element | null;
__baselineShell?: Element | null;
__baselineClickAt?: number | null;
__genericLoaderObserver?: MutationObserver;
__genericLoaderSeen?: boolean;
}
Expand Down Expand Up @@ -125,13 +124,6 @@ test('a delayed destination chunk exposes the route fallback without replacing t
await page.evaluate(() => {
window.__baselineAppLayout = document.querySelector('header')?.parentElement;
window.__baselineShell = document.querySelector('header');
window.__baselineClickAt = null;
document.addEventListener('click', (event) => {
const anchor = event.target instanceof Element ? event.target.closest('a[href]') : null;
if (anchor && new URL(anchor.href, window.location.href).pathname === '/app/attack') {
window.__baselineClickAt = performance.now();
}
}, true);
});
let destinationRequested = false;
await page.route('**/src/pages/AttackPage.tsx*', async (route) => {
Expand Down Expand Up @@ -178,27 +170,6 @@ test('a delayed destination chunk exposes the route fallback without replacing t
appLayout: document.querySelector('header')?.parentElement === window.__baselineAppLayout,
header: document.querySelector('header') === window.__baselineShell,
}))).toEqual({ appLayout: true, header: true });

const transitionMetrics = await page.evaluate(() => ({
clickTime: window.__baselineClickAt ?? null,
route: location.pathname,
shellDomIdentitySurvived: document.querySelector('header') === window.__baselineShell,
allRequestCount: performance.getEntriesByType('resource').length,
measuredRequestCount: performance.getEntriesByType('resource')
.filter((entry) => window.__baselineClickAt !== null && entry.startTime >= window.__baselineClickAt).length,
sourceRequestCount: performance.getEntriesByType('resource')
.filter((entry) => window.__baselineClickAt !== null && entry.startTime < window.__baselineClickAt).length,
allLongTaskCount: performance.getEntriesByType('longtask').length,
measuredLongTaskCount: performance.getEntriesByType('longtask')
.filter((entry) => window.__baselineClickAt !== null && entry.startTime >= window.__baselineClickAt).length,
}));
expect(transitionMetrics.clickTime).not.toBeNull();
expect(transitionMetrics.route).toBe('/app/attack');
expect(transitionMetrics.shellDomIdentitySurvived).toBe(true);
expect(transitionMetrics.sourceRequestCount).toBeGreaterThan(0);
expect(transitionMetrics.measuredRequestCount).toBeGreaterThan(0);
expect(transitionMetrics.measuredRequestCount).toBeLessThan(transitionMetrics.allRequestCount);
expect(transitionMetrics.measuredLongTaskCount).toBeLessThanOrEqual(transitionMetrics.allLongTaskCount);
});

test('a failed route chunk recovers after the offered reload', async ({ page }) => {
Expand Down Expand Up @@ -226,35 +197,6 @@ test('a failed route chunk recovers after the offered reload', async ({ page })
await expect(page.locator('header')).toBeVisible();
});

for (const [locale, openingLabel] of [
['es', 'Abriendo Atacar'],
['hu', 'Támadás megnyitása'],
] as const) {
test(`${locale} delayed route announces the localized destination`, async ({ page }) => {
await setLocale(page, locale);
await stubSupabaseSession(page);
await skipOnboarding(page);
await page.goto('/app', { waitUntil: 'domcontentloaded' });
await expect(homeToolLink(page, 'attack')).toBeVisible();

let destinationRequested = false;
await page.route('**/src/pages/AttackPage.tsx*', async (route) => {
destinationRequested = true;
await new Promise((resolve) => setTimeout(resolve, 1500));
await route.continue();
});

await homeToolLink(page, 'attack').click();

await expect.poll(() => destinationRequested).toBe(true);
const status = page.getByRole('status', { name: openingLabel, exact: true });
await expect(status).toHaveCount(1);
await expect(status).toHaveText(openingLabel);
await expect(status).not.toHaveText('Opening Attack');
await expect(page.locator('[data-route-skeleton="tool-form"][aria-busy="true"]')).toBeVisible();
});
}

async function gotoHome(page: Page) {
await setLocale(page, 'en');
await stubSupabaseSession(page);
Expand Down
19 changes: 0 additions & 19 deletions e2e/public-pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,3 @@ for (const locale of LOCALES) {
}
});
}

test('landing logos use the sized display asset with intrinsic dimensions', async ({ page }) => {
await setLocale(page, 'en');
await page.goto('/');

const logos = page.locator('img[alt="DebateLab logo"]');
await expect(logos).toHaveCount(2);
expect(await logos.evaluateAll((images) => images.map((image) => {
const logo = image as HTMLImageElement;
return {
path: new URL(logo.currentSrc).pathname,
width: logo.getAttribute('width'),
height: logo.getAttribute('height'),
};
}))).toEqual([
{ path: '/favicon-72.png', width: '36', height: '36' },
{ path: '/favicon-72.png', width: '32', height: '32' },
]);
});
16 changes: 0 additions & 16 deletions e2e/sparring.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,24 +132,8 @@ test('keeps the setup usable on mobile with reduced motion', async ({ page }) =>
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('/app/sparring', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('heading', { name: 'Sparring' })).toBeVisible();
const logo = page.getByAltText('DebateLab logo');
await expect(logo).toHaveAttribute('src', '/favicon-72.png');
await expect(logo).toHaveAttribute('width', '36');
await expect(logo).toHaveAttribute('height', '36');
await expect.poll(() => logo.evaluate((image: HTMLImageElement) => image.naturalWidth)).toBeGreaterThan(0);
await expect(page.getByRole('combobox', { name: 'Round format' })).toHaveText('School 4-6-6-3 · 8 speeches');
await expect(page.getByText('Write the resolution as you would hear it in the room.')).toHaveCount(0);
await expect(page.getByText('A precise motion gives the opponent something concrete to attack.')).toHaveCount(0);
await expect(page.getByText('Pro Introductor')).toBeHidden();
const speakingSummary = page.locator('main details > summary');
const summaryMetrics = await speakingSummary.evaluate((element) => {
const icon = element.querySelector('svg');
if (!icon) throw new Error('Speaking order disclosure is missing its chevron.');
const summaryBox = element.getBoundingClientRect();
const iconBox = icon.getBoundingClientRect();
return Math.abs((summaryBox.top + summaryBox.height / 2) - (iconBox.top + iconBox.height / 2));
});
expect(summaryMetrics).toBeLessThanOrEqual(1);
await page.getByText('Speaking order', { exact: true }).click();
await expect(page.getByText('Pro Introductor')).toBeVisible();
await expect(page.getByText('Pro Conclusor')).toBeVisible();
Expand Down
27 changes: 0 additions & 27 deletions e2e/startup-shell.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,13 @@ test('shows a dark private startup shell while the entry module is delayed', { t

await page.goto('/app', { waitUntil: 'commit' });
await expect(page.locator('[data-startup-shell]')).toBeVisible();
await expect(page.locator('#root > .seo-fallback')).toBeHidden();
await expect(page.locator('[data-startup-status]')).toHaveText('Loading DebateLab');
await expect.poll(() => page.evaluate(() => ({
appShell: document.documentElement.classList.contains('app-shell'),
dark: document.documentElement.classList.contains('dark'),
}))).toEqual({ appShell: true, dark: true });
});

test('shows the startup shell on login while the entry module is delayed', { tag: '@smoke' }, async ({ page }) => {
await setLocale(page, 'en');
await page.route('**/src/bootstrap.ts*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 1500));
await route.continue();
});

await page.goto('/login', { waitUntil: 'commit' });
await expect(page.locator('[data-startup-shell]')).toBeVisible();
await expect(page.locator('#root > .seo-fallback')).toBeHidden();
await expect.poll(() => page.evaluate(() => document.documentElement.classList.contains('app-shell'))).toBe(true);
});

test('mounts the app before Jam recorder modules resolve', { tag: '@smoke' }, async ({ page }) => {
await setLocale(page, 'en');
await stubSupabaseSession(page);
Expand All @@ -52,7 +38,6 @@ test('mounts the app before Jam recorder modules resolve', { tag: '@smoke' }, as

await page.goto('/app', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('main').getByRole('heading', { name: 'DebateLab', exact: true, level: 1 })).toBeVisible({ timeout: 5_000 });
await expect(page.locator('#root > .seo-fallback')).toHaveCount(0);
expect(await page.evaluate(() => {
const jam = (window as Window & { __jam__?: { exports?: { metadata?: unknown } } }).__jam__;
return typeof jam?.exports?.metadata === 'function';
Expand All @@ -67,15 +52,3 @@ test('shows a recoverable message when the static main entry fails', { tag: '@sm
await expect(page.getByRole('heading', { name: 'Unable to load DebateLab' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Reload' })).toBeEnabled();
});

test('keeps the branded shell visible before a delayed public entry module', { tag: '@smoke' }, async ({ page }) => {
await setLocale(page, 'en');
await page.route('**/src/bootstrap.ts*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 1500));
await route.continue();
});

await page.goto('/', { waitUntil: 'commit' });
await expect(page.locator('[data-startup-shell]')).toBeVisible();
await expect(page.getByText('DebateLab by Programcode is the official')).toHaveCount(0);
});
9 changes: 9 additions & 0 deletions services/collaboration/src/postgres-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ describe("Postgres collaboration writer locks", () => {
documentId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
epoch: 3,
})).resolves.toBe(true);
expect(client.query).toHaveBeenCalledWith(
expect.stringContaining("member.user_id = $3"),
[
"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
3,
"cccccccc-cccc-4ccc-8ccc-cccccccccccc",
false,
],
);
expect(client.release).toHaveBeenCalledTimes(1);
});

Expand Down
6 changes: 0 additions & 6 deletions src/test/components/EnvironmentSwitcher.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ describe('EnvironmentSwitcher', () => {
expect(reload).not.toHaveBeenCalled();
});

it('offers production while staging is active', () => {
mocks.env = 'staging';
render(<EnvironmentSwitcher reload={vi.fn()} />);
expect(screen.getByRole('button', { name: 'Switch to Production' })).toBeInTheDocument();
});

it('still switches when sign-out fails, so nobody is stranded on a broken project', async () => {
mocks.env = 'staging';
mocks.signOut.mockRejectedValue(new Error('staging unreachable'));
Expand Down
7 changes: 6 additions & 1 deletion src/test/debate/debate-context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,13 @@ describe('Current Debate context in requests and the Review control', () => {
}
});

it('sends no debate material in one-off mode and says so above the button', () => {
it('sends no debate material in one-off mode and says so above the button', async () => {
enableOneOffMode();
setMockInvokeResponse({ output: { needsMoreInfo: false, clarifyingQuestions: [], topFive: [], nextSteps: [], warningsAndTradeoffs: [] } });
await brainstorm(brainstormInput, config);
expect(lastBody().debateContext).toBeUndefined();
expect(lastBody().sourceIds).toEqual([]);

render(<DebateContextControl feature="generate" side="pro" />);
expect(screen.getByTestId('debate-context-control')).toHaveTextContent('One-off mode: no Current Debate material will be used or saved.');
});
Expand Down
Loading
Loading