mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* feat: configure prettier with pre-commit hook integration Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo root with husky pre-commit hook integration. Moves husky from gitnexus/ to root package.json for reliable hook installation. - Root package.json with prepare/format/format:check scripts - .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4 - .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md - .gitattributes enforcing LF line endings for Windows consistency - Pre-commit hook uses direct node_modules/.bin/ paths (no npx) * style: apply prettier formatting to entire codebase One-time bulk format. No logic changes. Use .git-blame-ignore-revs to skip this commit in git blame. * chore: add .git-blame-ignore-revs for prettier format commit * perf: pre-commit hook runs only tests related to staged files Use vitest --related to scope test execution to tests that import the changed files, instead of running the full suite on every commit. * perf: remove vitest from pre-commit hook, keep in CI only Pre-commit now runs lint-staged + tsc only. Tests run in CI (ci-tests.yml) where they belong — keeps commits fast. * ci: add prettier format check to quality workflow PRs will now fail if code isn't formatted with prettier.
155 lines
5.8 KiB
TypeScript
155 lines
5.8 KiB
TypeScript
import { test, expect, type TestInfo } from '@playwright/test';
|
|
|
|
/**
|
|
* E2E tests for the GitNexus web UI — exploring view features.
|
|
*
|
|
* Requires:
|
|
* - gitnexus serve running on localhost:4747 with at least one indexed repo
|
|
* - gitnexus-web dev server running on localhost:5173
|
|
*
|
|
* Skipped when servers aren't available (CI without services, etc.).
|
|
* Set E2E=1 to force-run even without the availability check.
|
|
*/
|
|
|
|
const BACKEND_URL = process.env.BACKEND_URL ?? 'http://localhost:4747';
|
|
const FRONTEND_URL = process.env.FRONTEND_URL ?? 'http://localhost:5173';
|
|
|
|
test.beforeAll(async () => {
|
|
if (process.env.E2E) return;
|
|
try {
|
|
const [backendRes, frontendRes] = await Promise.allSettled([
|
|
fetch(`${BACKEND_URL}/api/repos`),
|
|
fetch(FRONTEND_URL),
|
|
]);
|
|
if (
|
|
backendRes.status === 'rejected' ||
|
|
(backendRes.status === 'fulfilled' && !backendRes.value.ok)
|
|
) {
|
|
test.skip(true, 'gitnexus serve not available on :4747');
|
|
return;
|
|
}
|
|
if (
|
|
frontendRes.status === 'rejected' ||
|
|
(frontendRes.status === 'fulfilled' && !frontendRes.value.ok)
|
|
) {
|
|
test.skip(true, 'Vite dev server not available on :5173');
|
|
return;
|
|
}
|
|
// Check there's at least one indexed repo
|
|
if (backendRes.status === 'fulfilled') {
|
|
const repos = await backendRes.value.json();
|
|
if (!repos.length) {
|
|
test.skip(true, 'No indexed repos — run gitnexus analyze first');
|
|
return;
|
|
}
|
|
}
|
|
} catch {
|
|
test.skip(true, 'servers not available');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Wait for the new auto-connect flow to complete.
|
|
*
|
|
* The app now auto-detects the server via polling and connects without
|
|
* any user interaction. We just need to wait for the exploring view.
|
|
*/
|
|
async function waitForGraphLoaded(page: import('@playwright/test').Page, testInfo: TestInfo) {
|
|
await page.goto('/');
|
|
|
|
// The app auto-connects: onboarding → success → loading → exploring.
|
|
// Wait for the status bar "Ready" indicator which confirms the graph is loaded.
|
|
await expect(page.locator('[data-testid="status-ready"]')).toBeVisible({ timeout: 30_000 });
|
|
await expect(page.getByText(/\d+ nodes/).first()).toBeVisible();
|
|
await page.screenshot({ path: testInfo.outputPath('graph-loaded.png') });
|
|
}
|
|
|
|
test.describe('Server Connection & Graph Loading', () => {
|
|
test('auto-connects and loads graph', async ({ page }, testInfo) => {
|
|
await waitForGraphLoaded(page, testInfo);
|
|
await page.screenshot({ path: testInfo.outputPath('graph-loaded-full.png'), fullPage: true });
|
|
});
|
|
});
|
|
|
|
test.describe('Nexus AI', () => {
|
|
test('panel opens and agent initializes without error', async ({ page }, testInfo) => {
|
|
await waitForGraphLoaded(page, testInfo);
|
|
|
|
await page.getByRole('button', { name: 'Nexus AI' }).click();
|
|
await expect(page.getByText('Ask me anything')).toBeVisible({ timeout: 15_000 });
|
|
await page.screenshot({ path: testInfo.outputPath('nexus-ai-panel.png'), fullPage: true });
|
|
|
|
const errorBanner = page.getByText('Database not ready');
|
|
expect(await errorBanner.isVisible().catch(() => false)).toBe(false);
|
|
});
|
|
});
|
|
|
|
test.describe('Processes Panel', () => {
|
|
test('shows process list and View button works', async ({ page }, testInfo) => {
|
|
await waitForGraphLoaded(page, testInfo);
|
|
|
|
await page.getByRole('button', { name: 'Nexus AI' }).click();
|
|
await page.getByText('Processes').click();
|
|
|
|
await expect(page.locator('[data-testid="process-list-loaded"]')).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
await page.screenshot({ path: testInfo.outputPath('processes-panel.png'), fullPage: true });
|
|
|
|
const processRow = page.locator('[data-testid="process-row"]').first();
|
|
await expect(processRow).toBeVisible({ timeout: 10_000 });
|
|
await processRow.hover();
|
|
|
|
const viewBtn = processRow.locator('[data-testid="process-view-button"]');
|
|
await viewBtn.waitFor({ state: 'visible', timeout: 5_000 });
|
|
await viewBtn.click();
|
|
await expect(page.locator('[data-testid="process-modal"]')).toBeVisible({ timeout: 5_000 });
|
|
await page.screenshot({
|
|
path: testInfo.outputPath('process-view-clicked.png'),
|
|
fullPage: true,
|
|
});
|
|
});
|
|
|
|
test('lightbulb highlights nodes in graph', async ({ page }, testInfo) => {
|
|
await waitForGraphLoaded(page, testInfo);
|
|
|
|
await page.getByRole('button', { name: 'Nexus AI' }).click();
|
|
await page.getByText('Processes').click();
|
|
await expect(page.locator('[data-testid="process-list-loaded"]')).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
const processRow = page.locator('[data-testid="process-row"]').first();
|
|
await expect(processRow).toBeVisible({ timeout: 10_000 });
|
|
await processRow.hover();
|
|
|
|
const lightbulb = processRow.locator('[data-testid="process-highlight-button"]');
|
|
await lightbulb.waitFor({ state: 'visible', timeout: 5_000 });
|
|
await lightbulb.click();
|
|
await expect(processRow).toHaveClass(/bg-amber-950/, { timeout: 5_000 });
|
|
await page.screenshot({ path: testInfo.outputPath('after-highlight.png'), fullPage: true });
|
|
});
|
|
});
|
|
|
|
test.describe('Turn Off All Highlights', () => {
|
|
test('selecting a node dims others, button clears it', async ({ page }, testInfo) => {
|
|
await waitForGraphLoaded(page, testInfo);
|
|
|
|
await expect(page.locator('canvas').first()).toBeVisible({ timeout: 10_000 });
|
|
|
|
const fileItem = page.getByText('package.json').first();
|
|
await expect(fileItem).toBeVisible({ timeout: 10_000 });
|
|
await fileItem.click();
|
|
|
|
const highlightToggle = page.locator('[data-testid="ai-highlights-toggle"]');
|
|
await expect(highlightToggle).toHaveAttribute('title', 'Turn off all highlights', {
|
|
timeout: 5_000,
|
|
});
|
|
|
|
await highlightToggle.click();
|
|
await expect(highlightToggle).toHaveAttribute('title', 'Turn on AI highlights', {
|
|
timeout: 5_000,
|
|
});
|
|
await page.screenshot({ path: testInfo.outputPath('highlights-cleared.png'), fullPage: true });
|
|
});
|
|
});
|