Add research workspace and graphing flows

This commit is contained in:
2026-03-07 16:52:35 -05:00
parent db01f207a5
commit 62bacdf104
37 changed files with 5494 additions and 434 deletions

View File

@@ -1,17 +1,51 @@
import { expect, test } from '@playwright/test';
import { expect, test, type Page } from '@playwright/test';
const PASSWORD = 'Sup3rSecure!123';
test('redirects protected routes to sign in and preserves the return path', async ({ page }) => {
await page.goto('/analysis?ticker=nvda');
test.describe.configure({ mode: 'serial' });
function createDeferred() {
let resolve: (() => void) | null = null;
const promise = new Promise<void>((done) => {
resolve = done;
});
return {
promise,
resolve: () => resolve?.()
};
}
async function gotoAuthPage(page: Page, path: string) {
await page.goto(path, { waitUntil: 'domcontentloaded' });
await page.waitForLoadState('networkidle');
}
test('preserves the return path while switching between auth screens and shows the expected controls', async ({ page }) => {
await gotoAuthPage(page, '/auth/signin?next=%2Fanalysis%3Fticker%3DNVDA');
await expect(page).toHaveURL(/\/auth\/signin\?/);
await expect(page.getByRole('heading', { name: 'Secure Sign In' })).toBeVisible();
expect(new URL(page.url()).searchParams.get('next')).toBe('/analysis?ticker=nvda');
await expect(page.getByText('Use email/password or request a magic link.')).toBeVisible();
await expect(page.locator('input[autocomplete="email"]')).toBeVisible();
await expect(page.locator('input[autocomplete="current-password"]')).toBeVisible();
await expect(page.getByRole('button', { name: 'Sign in with password' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Send magic link' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Create one' })).toHaveAttribute('href', '/auth/signup?next=%2Fanalysis%3Fticker%3DNVDA');
await page.getByRole('link', { name: 'Create one' }).click();
await expect(page).toHaveURL(/\/auth\/signup\?next=%2Fanalysis%3Fticker%3DNVDA$/);
await expect(page.getByRole('heading', { name: 'Create Account' })).toBeVisible();
await expect(page.getByText('Set up your operator profile to access portfolio and filings intelligence.')).toBeVisible();
await expect(page.locator('input[autocomplete="name"]')).toBeVisible();
await expect(page.locator('input[autocomplete="email"]')).toBeVisible();
await expect(page.locator('input[autocomplete="new-password"]').first()).toBeVisible();
await expect(page.getByRole('button', { name: 'Create account' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Sign in' })).toHaveAttribute('href', '/auth/signin?next=%2Fanalysis%3Fticker%3DNVDA');
});
test('shows client-side validation when signup passwords do not match', async ({ page }) => {
await page.goto('/auth/signup');
await gotoAuthPage(page, '/auth/signup');
await page.locator('input[autocomplete="name"]').fill('Playwright User');
await page.locator('input[autocomplete="email"]').fill('mismatch@example.com');
@@ -22,17 +56,57 @@ test('shows client-side validation when signup passwords do not match', async ({
await expect(page.getByText('Passwords do not match.')).toBeVisible();
});
test('creates a new account and lands on the command center', async ({ page }) => {
const email = `playwright-${Date.now()}@example.com`;
test('shows loading affordances while sign-in is in flight', async ({ page }) => {
const gate = createDeferred();
await page.goto('/auth/signup');
await page.route('**/api/auth/sign-in/email', async (route) => {
await gate.promise;
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ message: 'Invalid credentials' })
});
});
await gotoAuthPage(page, '/auth/signin');
await page.locator('input[autocomplete="email"]').fill('playwright@example.com');
await page.locator('input[autocomplete="current-password"]').fill(PASSWORD);
const submitButton = page.getByRole('button', { name: 'Sign in with password' });
const magicLinkButton = page.getByRole('button', { name: 'Send magic link' });
await submitButton.click();
await expect(page.getByRole('button', { name: 'Signing in...' })).toBeDisabled();
await expect(magicLinkButton).toBeDisabled();
gate.resolve();
await expect(page.getByText('Invalid credentials')).toBeVisible();
});
test('shows loading affordances while sign-up is in flight', async ({ page }) => {
const gate = createDeferred();
await page.route('**/api/auth/sign-up/email', async (route) => {
await gate.promise;
await route.fulfill({
status: 409,
contentType: 'application/json',
body: JSON.stringify({ message: 'Email already exists' })
});
});
await gotoAuthPage(page, '/auth/signup');
await page.locator('input[autocomplete="name"]').fill('Playwright User');
await page.locator('input[autocomplete="email"]').fill(email);
await page.locator('input[autocomplete="email"]').fill(`playwright-${Date.now()}@example.com`);
await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD);
await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD);
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page).toHaveURL(/\/$/);
await expect(page.getByRole('heading', { name: 'Command Center' })).toBeVisible();
await expect(page.getByText('Quick Links')).toBeVisible();
await expect(page.getByRole('button', { name: 'Creating account...' })).toBeDisabled();
gate.resolve();
await expect(page.getByText('Email already exists')).toBeVisible();
});