test(FN-000): resolve dashboard test noise
This commit is contained in:
29
.github/workflows/pr-checks.yml
vendored
29
.github/workflows/pr-checks.yml
vendored
@@ -61,6 +61,32 @@ jobs:
|
|||||||
- name: Typecheck
|
- name: Typecheck
|
||||||
run: pnpm typecheck
|
run: pnpm typecheck
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: "24"
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Install Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
test-shards:
|
test-shards:
|
||||||
name: Test shard ${{ matrix.shard }}/3
|
name: Test shard ${{ matrix.shard }}/3
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -88,8 +114,5 @@ jobs:
|
|||||||
- name: Install Bun
|
- name: Install Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Test (deterministic shard)
|
- name: Test (deterministic shard)
|
||||||
run: pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3
|
run: pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ why it broke, how it was fixed, and what command verified the fix.
|
|||||||
|
|
||||||
| ID | Area | Symptom | Root cause | Planned fix | Status |
|
| ID | Area | Symptom | Root cause | Planned fix | Status |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| TSH-007 | Dashboard test runtime/noise | `pnpm test:full` passes but dashboard tests take about 10 minutes locally and emit repeated SQLite experimental warnings plus git default-branch hints. | The suite exercises many SQLite-backed and git-backed paths; Node emits SQLite warnings globally and fixture git repos use default branch initialization. | Consider a follow-up cleanup to quiet expected warnings and split expensive dashboard lanes without reducing coverage. | Open |
|
| _None_ | | | | | |
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
@@ -23,3 +23,8 @@ why it broke, how it was fixed, and what command verified the fix.
|
|||||||
| TSH-005 | Plugin changed-test targeting | Plugin edits still forced the entire suite after package-aware targeting was added. | `shouldForceFullSuite()` treated every `plugins/**` path as broad repo surface area. | Plugin workspace paths are now resolved like package paths, so plugin edits run the relevant plugin tests instead of automatically falling back to all tests. | `node --test scripts/__tests__/test-changed.test.mjs` |
|
| TSH-005 | Plugin changed-test targeting | Plugin edits still forced the entire suite after package-aware targeting was added. | `shouldForceFullSuite()` treated every `plugins/**` path as broad repo surface area. | Plugin workspace paths are now resolved like package paths, so plugin edits run the relevant plugin tests instead of automatically falling back to all tests. | `node --test scripts/__tests__/test-changed.test.mjs` |
|
||||||
| TSH-006 | Skipped-test inventory | The skipped-test inventory documented the wrong gate ownership for the new and legacy extension suites. | The maintained extension integration lane and legacy exhaustive gate were renamed during implementation, but the inventory text still reflected the earlier split. | `docs/skipped-test-inventory.md` now lists `FUSION_TEST_EXTENSION_INTEGRATION` for the maintained built-extension test and `FUSION_TEST_LEGACY_EXTENSION_INTEGRATION` for the legacy suite. | `git diff --check`, `node --test scripts/__tests__/test-governance.test.mjs`, and `pnpm test:full` |
|
| TSH-006 | Skipped-test inventory | The skipped-test inventory documented the wrong gate ownership for the new and legacy extension suites. | The maintained extension integration lane and legacy exhaustive gate were renamed during implementation, but the inventory text still reflected the earlier split. | `docs/skipped-test-inventory.md` now lists `FUSION_TEST_EXTENSION_INTEGRATION` for the maintained built-extension test and `FUSION_TEST_LEGACY_EXTENSION_INTEGRATION` for the legacy suite. | `git diff --check`, `node --test scripts/__tests__/test-governance.test.mjs`, and `pnpm test:full` |
|
||||||
| TSH-008 | Test isolation | `custom-providers.test.ts` wrote to the real `~/.fusion/settings.json` path during the first local full-suite run. | The test used `os.homedir()` directly instead of a fixture home directory. | `readCustomProviders()` now accepts an injectable home directory, and the test uses a temp HOME fixture that is removed after each case. | `pnpm --filter @fusion/engine test -- src/__tests__/custom-providers.test.ts` (ran the full engine lane: 108 files, 3339 tests) |
|
| TSH-008 | Test isolation | `custom-providers.test.ts` wrote to the real `~/.fusion/settings.json` path during the first local full-suite run. | The test used `os.homedir()` directly instead of a fixture home directory. | `readCustomProviders()` now accepts an injectable home directory, and the test uses a temp HOME fixture that is removed after each case. | `pnpm --filter @fusion/engine test -- src/__tests__/custom-providers.test.ts` (ran the full engine lane: 108 files, 3339 tests) |
|
||||||
|
| TSH-007 | Dashboard runtime/noise | `pnpm test:full` passed but dashboard tests took about 10 minutes locally and emitted repeated SQLite experimental warnings plus git default-branch hints. | Dashboard tests mix real SQLite-backed route coverage, git-backed route fixtures, and jsdom UI suites in one package lane. Node emits `node:sqlite` experimental warnings globally, some git fixtures used plain `git init`, and dashboard tests used deprecated `environmentMatchGlobs`. | Dashboard tests now use explicit Vitest projects for `dashboard-app` and `dashboard-api`, package scripts expose `test:app`/`test:api`/`test:browser-smoke` lanes, the shared Vitest setup filters known SQLite experimental warnings, fixture repos initialize with `main`, and low-value agent CSS class-presence assertions were cut in favor of focused layout/token/no-inline-style contracts. | `pnpm --filter @fusion/dashboard test:app` (287 files, 7254 tests), `pnpm --filter @fusion/dashboard test:api` (132 files, 3493 tests), `pnpm --filter @fusion/core exec vitest run src/__tests__/central-db.test.ts --silent=passed-only --reporter=dot`, and `pnpm test:full` |
|
||||||
|
| TSH-009 | PR action minutes | PR test shards built the whole workspace before running tests, duplicating the separate build gate. | `pr-checks.yml` had a `pnpm build` step inside every test-shard job. | Removed the pre-test build from PR test shards; package tests that require artifacts build their own fixtures, while `build` remains an explicit PR workflow job. | `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot` and `pnpm build` |
|
||||||
|
| TSH-010 | Dashboard lane scripts | `pnpm --filter @fusion/dashboard test:api` failed with "No test files found" after converting dashboard to Vitest projects. | Positional glob filters were evaluated against all projects instead of selecting the intended dashboard project. | Dashboard lane scripts now use `--project dashboard-app` and `--project dashboard-api`. | `pnpm --filter @fusion/dashboard test:api` and `pnpm --filter @fusion/dashboard exec vitest run --project dashboard-app app/__tests__/agent-css-classes.test.ts --silent=passed-only --reporter=dot` |
|
||||||
|
| TSH-011 | PR build coverage | While reducing PR shard minutes, the workflow temporarily had no standalone `pnpm build` job. | The old workflow only had lint, typecheck, and test shards, so removing the shard-local build also removed PR build coverage. | Added an explicit `build` job to `pr-checks.yml` and updated the workflow contract test to require it. | `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot` and `pnpm build` |
|
||||||
|
| TSH-012 | Warning filtering | The first SQLite warning filter also installed a `process.on("warning")` listener that could duplicate unrelated warnings. | Node still prints non-filtered warnings by default, so manually writing them again made future warning output noisier. | The shared setup now only wraps `process.emitWarning` for the known SQLite experimental warning, and the dashboard noisy-output marker list no longer suppresses generic trace-warning guidance. | `pnpm --filter @fusion/core typecheck`, `pnpm --filter @fusion/core exec vitest run src/__tests__/central-db.test.ts --silent=passed-only --reporter=dot`, and `pnpm test:full` |
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ GitHub Actions now runs deterministic test sharding via `pnpm test:ci:shard --sh
|
|||||||
- `pnpm test:full` remains the canonical full local suite.
|
- `pnpm test:full` remains the canonical full local suite.
|
||||||
- `pnpm verify:workspace` remains the canonical local lint -> test -> build gate.
|
- `pnpm verify:workspace` remains the canonical local lint -> test -> build gate.
|
||||||
|
|
||||||
`test:ci:shard` is a CI-focused entrypoint (`scripts/ci-test-shard.mjs`) that partitions a fixed package list by shard index modulo total shard count so coverage is deterministic and reproducible.
|
`test:ci:shard` is a CI-focused entrypoint (`scripts/ci-test-shard.mjs`) that partitions workspace packages with `test` scripts by shard index modulo total shard count so coverage is deterministic and reproducible.
|
||||||
|
|
||||||
`pnpm test` now uses a changed-only entrypoint (`scripts/test-changed.mjs`) for faster local iteration. It resolves the comparison base from `.changeset/config.json` (`baseBranch`) and runs only affected workspaces from `pnpm-workspace.yaml` (both `packages/*` and `plugins/**`) using safe package-first filtering (`pnpm --filter <pkg> test`). It automatically falls back to the full suite when the run is forced (CI / `--full`), the git comparison base or diff cannot be resolved, no changes are detected, shared/root test infrastructure changes, or changed workspace paths cannot be resolved to a workspace package (fail-safe coverage behavior).
|
`pnpm test` now uses a changed-only entrypoint (`scripts/test-changed.mjs`) for faster local iteration. It resolves the comparison base from `.changeset/config.json` (`baseBranch`) and runs only affected workspaces from `pnpm-workspace.yaml` (both `packages/*` and `plugins/**`) using safe package-first filtering (`pnpm --filter <pkg> test`). It automatically falls back to the full suite when the run is forced (CI / `--full`), the git comparison base or diff cannot be resolved, no changes are detected, shared/root test infrastructure changes, or changed workspace paths cannot be resolved to a workspace package (fail-safe coverage behavior).
|
||||||
|
|
||||||
@@ -159,6 +159,19 @@ pnpm --filter @runfusion/fusion test:extension-integration
|
|||||||
|
|
||||||
`test:extension-integration` enables `FUSION_TEST_EXTENSION_INTEGRATION=1` and runs the full fn pi extension integration suite. It remains an explicit opt-in lane so default workspace verification stays fast, while still providing a discoverable command for full extension-tool integration coverage.
|
`test:extension-integration` enables `FUSION_TEST_EXTENSION_INTEGRATION=1` and runs the full fn pi extension integration suite. It remains an explicit opt-in lane so default workspace verification stays fast, while still providing a discoverable command for full extension-tool integration coverage.
|
||||||
|
|
||||||
|
## Dashboard Test Lanes
|
||||||
|
|
||||||
|
Dashboard tests are split into explicit local lanes. The default dashboard package gate runs the app/API tests; the built-client contract remains a separate lane because it performs its own production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @fusion/dashboard test:app # React/jsdom app tests
|
||||||
|
pnpm --filter @fusion/dashboard test:api # Node API/server tests
|
||||||
|
pnpm --filter @fusion/dashboard test:browser-smoke # local browser layout smoke
|
||||||
|
pnpm --filter @fusion/dashboard test:build # built client output contract
|
||||||
|
```
|
||||||
|
|
||||||
|
Use these lanes for local iteration before PRs. The full workspace test gate runs the default dashboard app/API suite, `pnpm build` remains an explicit PR gate, and PR test shards avoid a redundant pre-test workspace build to save GitHub Actions minutes.
|
||||||
|
|
||||||
## Release Process
|
## Release Process
|
||||||
|
|
||||||
Fusion uses Changesets + version PR workflow.
|
Fusion uses Changesets + version PR workflow.
|
||||||
|
|||||||
@@ -171,11 +171,22 @@ describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
|
|||||||
it("uses the same deterministic test sharding command as manual CI", () => {
|
it("uses the same deterministic test sharding command as manual CI", () => {
|
||||||
expect(workflow.jobs?.lint).toBeDefined();
|
expect(workflow.jobs?.lint).toBeDefined();
|
||||||
expect(workflow.jobs?.typecheck).toBeDefined();
|
expect(workflow.jobs?.typecheck).toBeDefined();
|
||||||
|
expect(workflow.jobs?.build).toBeDefined();
|
||||||
expect(workflow.jobs?.["test-shards"]).toBeDefined();
|
expect(workflow.jobs?.["test-shards"]).toBeDefined();
|
||||||
expect(workflow.jobs?.["test-shards"]?.strategy?.matrix?.shard).toEqual([1, 2, 3]);
|
expect(workflow.jobs?.["test-shards"]?.strategy?.matrix?.shard).toEqual([1, 2, 3]);
|
||||||
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3");
|
expect(content).toContain("pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3");
|
||||||
expect(content).not.toContain("run: pnpm test\n");
|
expect(content).not.toContain("run: pnpm test\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps build coverage as an explicit PR gate", () => {
|
||||||
|
const buildSteps = workflow.jobs?.build?.steps ?? [];
|
||||||
|
expect(buildSteps.some((step: any) => step.name === "Build" && step.run === "pnpm build")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not spend PR action minutes on a pre-test workspace build", () => {
|
||||||
|
const testSteps = workflow.jobs?.["test-shards"]?.steps ?? [];
|
||||||
|
expect(testSteps.some((step: any) => step.name === "Build" || step.run === "pnpm build")).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Version & Release workflow (.github/workflows/version.yml)", () => {
|
describe("Version & Release workflow (.github/workflows/version.yml)", () => {
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ const fsPromises = requireFromHere("node:fs/promises") as FsPromisesModule;
|
|||||||
const childProcess = requireFromHere("node:child_process") as ChildProcessModule;
|
const childProcess = requireFromHere("node:child_process") as ChildProcessModule;
|
||||||
const { mkdtempSync, mkdirSync, rmSync, realpathSync, existsSync } = fs;
|
const { mkdtempSync, mkdirSync, rmSync, realpathSync, existsSync } = fs;
|
||||||
|
|
||||||
|
function installWarningFilter(): void {
|
||||||
|
const warningState = globalThis as typeof globalThis & { __fusionTestWarningFilterInstalled?: boolean };
|
||||||
|
if (warningState.__fusionTestWarningFilterInstalled) return;
|
||||||
|
warningState.__fusionTestWarningFilterInstalled = true;
|
||||||
|
|
||||||
|
const originalEmitWarning = process.emitWarning.bind(process);
|
||||||
|
process.emitWarning = ((warning: string | Error, ...args: unknown[]) => {
|
||||||
|
const warningText = warning instanceof Error ? warning.message : warning;
|
||||||
|
const warningType = typeof args[0] === "string" ? args[0] : undefined;
|
||||||
|
if (warningType === "ExperimentalWarning" && warningText.includes("SQLite is an experimental feature")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return originalEmitWarning(warning as string, ...(args as Parameters<typeof process.emitWarning> extends [any, ...infer Rest] ? Rest : never));
|
||||||
|
}) as typeof process.emitWarning;
|
||||||
|
}
|
||||||
|
|
||||||
|
installWarningFilter();
|
||||||
|
|
||||||
const TEST_HOME_PREFIX = "fn-test-home-";
|
const TEST_HOME_PREFIX = "fn-test-home-";
|
||||||
const DEFAULT_TEST_SUBPROCESS_TIMEOUT_MS = Math.max(
|
const DEFAULT_TEST_SUBPROCESS_TIMEOUT_MS = Math.max(
|
||||||
1_000,
|
1_000,
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ function hasClass(cls: string): boolean {
|
|||||||
return new RegExp(`${escaped}(?=[\\s,{:.#>+~])`).test(stylesContent);
|
return new RegExp(`${escaped}(?=[\\s,{:.#>+~])`).test(stylesContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractSection(startMarker: string, endMarker: string): string {
|
|
||||||
const start = stylesContent.indexOf(startMarker);
|
|
||||||
const end = stylesContent.indexOf(endMarker, start + startMarker.length);
|
|
||||||
return start >= 0 && end >= 0 ? stylesContent.slice(start, end) : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Agent CSS classes", () => {
|
describe("Agent CSS classes", () => {
|
||||||
// Verify agent state CSS variables are defined in the global stylesheet
|
// Verify agent state CSS variables are defined in the global stylesheet
|
||||||
it("should define --state-* CSS variables", () => {
|
it("should define --state-* CSS variables", () => {
|
||||||
@@ -48,109 +42,6 @@ describe("Agent CSS classes", () => {
|
|||||||
expect(stylesContent).toContain("--state-error-border:");
|
expect(stylesContent).toContain("--state-error-border:");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify BEM button modifier classes exist
|
|
||||||
it("should define BEM button modifier classes", () => {
|
|
||||||
expect(hasClass(".btn--sm")).toBe(true);
|
|
||||||
expect(hasClass(".btn--primary")).toBe(true);
|
|
||||||
expect(hasClass(".btn--danger")).toBe(true);
|
|
||||||
expect(hasClass(".btn--warning")).toBe(true);
|
|
||||||
expect(hasClass(".btn--compact")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify badge base class
|
|
||||||
it("should define .badge base class", () => {
|
|
||||||
expect(hasClass(".badge")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify AgentMetricsBar classes
|
|
||||||
it("should define AgentMetricsBar CSS classes", () => {
|
|
||||||
expect(hasClass(".agent-metrics-bar")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-card")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-card--active")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-card--tasks")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-card--success")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-card--runs")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-info")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-value")).toBe(true);
|
|
||||||
expect(hasClass(".agent-metric-label")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify AgentsView classes
|
|
||||||
it("should define AgentsView CSS classes", () => {
|
|
||||||
expect(hasClass(".agents-view")).toBe(true);
|
|
||||||
expect(hasClass(".agents-view-header")).toBe(true);
|
|
||||||
expect(hasClass(".agents-view-title")).toBe(true);
|
|
||||||
expect(hasClass(".agents-view-controls")).toBe(true);
|
|
||||||
expect(hasClass(".agents-view-primary-actions")).toBe(true);
|
|
||||||
expect(hasClass(".agents-view-content")).toBe(true);
|
|
||||||
expect(hasClass(".agents-overview-bar")).toBe(true);
|
|
||||||
expect(hasClass(".agents-overview-bar__toggle")).toBe(true);
|
|
||||||
expect(hasClass(".agents-overview-bar__content")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls-trigger")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls-trigger--active")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls-panel")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls-filters")).toBe(true);
|
|
||||||
expect(hasClass(".agent-state-filter")).toBe(true);
|
|
||||||
expect(hasClass(".agent-state-filter-select")).toBe(true);
|
|
||||||
expect(hasClass(".agent-system-filter")).toBe(true);
|
|
||||||
expect(hasClass(".agent-controls-actions")).toBe(true);
|
|
||||||
expect(hasClass(".agent-global-controls")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-shell")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-controls")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-viewport")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-canvas")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-canvas--zoom-75")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-canvas--zoom-100")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-canvas--zoom-125")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart-canvas--zoom-150")).toBe(true);
|
|
||||||
expect(hasClass(".agent-org-chart--vertical")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card--idle")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card--active")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card--running")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card--paused")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-card--error")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-header")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-icon")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-badge")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badge--idle")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badge--active")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badge--running")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badge--paused")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badge--error")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-health")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-name")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-id")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-clickable")).toBe(true);
|
|
||||||
expect(hasClass(".agent-board-actions")).toBe(true);
|
|
||||||
expect(hasClass(".agent-list")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card--idle")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card--active")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card--running")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card--paused")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card--error")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card-header")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card-body")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card-actions")).toBe(true);
|
|
||||||
expect(hasClass(".agent-info")).toBe(true);
|
|
||||||
expect(hasClass(".agent-info--clickable")).toBe(true);
|
|
||||||
expect(hasClass(".agent-icon")).toBe(true);
|
|
||||||
expect(hasClass(".agent-icon--clickable")).toBe(true);
|
|
||||||
expect(hasClass(".agent-meta")).toBe(true);
|
|
||||||
expect(hasClass(".agent-name")).toBe(true);
|
|
||||||
expect(hasClass(".agent-id")).toBe(true);
|
|
||||||
expect(hasClass(".agent-badges")).toBe(true);
|
|
||||||
expect(hasClass(".agent-card-chevron")).toBe(true);
|
|
||||||
expect(hasClass(".agent-task")).toBe(true);
|
|
||||||
expect(hasClass(".agent-heartbeat")).toBe(true);
|
|
||||||
expect(hasClass(".agent-role-select")).toBe(true);
|
|
||||||
expect(hasClass(".agent-empty")).toBe(true);
|
|
||||||
expect(hasClass(".spin")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps split layout as the scroll-constrained pane container", () => {
|
it("keeps split layout as the scroll-constrained pane container", () => {
|
||||||
const splitLayout = extractRuleBlock(".agents-split-layout");
|
const splitLayout = extractRuleBlock(".agents-split-layout");
|
||||||
expect(splitLayout).toContain("flex: 1");
|
expect(splitLayout).toContain("flex: 1");
|
||||||
@@ -218,7 +109,6 @@ describe("Agent CSS classes", () => {
|
|||||||
expect(orgChartSection).not.toMatch(/1\.5rem|0\.75rem|0\.72rem|0\.78rem|0\.65rem|120ms\s+ease|10px/);
|
expect(orgChartSection).not.toMatch(/1\.5rem|0\.75rem|0\.72rem|0\.78rem|0\.65rem|120ms\s+ease|10px/);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify AgentDetailView classes
|
|
||||||
it("encodes compact mobile agent detail header layout contracts", () => {
|
it("encodes compact mobile agent detail header layout contracts", () => {
|
||||||
const css = fs.readFileSync(path.join(__dirname, "../components/AgentDetailView.css"), "utf-8");
|
const css = fs.readFileSync(path.join(__dirname, "../components/AgentDetailView.css"), "utf-8");
|
||||||
const mobileStart = css.indexOf("@media (max-width: 768px)");
|
const mobileStart = css.indexOf("@media (max-width: 768px)");
|
||||||
@@ -232,117 +122,6 @@ describe("Agent CSS classes", () => {
|
|||||||
expect(mobileCss).toContain(".agent-detail-mobile-icon-control .agent-detail-control-label {");
|
expect(mobileCss).toContain(".agent-detail-mobile-icon-control .agent-detail-control-label {");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should define AgentDetailView CSS classes", () => {
|
|
||||||
expect(hasClass(".agent-detail-overlay")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-modal")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-loading")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-header")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-title")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-icon")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-info")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-badges")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-actions")).toBe(true);
|
|
||||||
// Redesigned compact header structure
|
|
||||||
expect(hasClass(".agent-detail-identity")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-inline-back")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-controls")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-utility-actions")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-tabs")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-tab")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-content")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-footer")).toBe(true);
|
|
||||||
expect(hasClass(".agent-detail-id")).toBe(true);
|
|
||||||
expect(hasClass(".dashboard-tab")).toBe(true);
|
|
||||||
expect(hasClass(".dashboard-section")).toBe(true);
|
|
||||||
expect(hasClass(".info-grid")).toBe(true);
|
|
||||||
expect(hasClass(".info-item")).toBe(true);
|
|
||||||
expect(hasClass(".info-label")).toBe(true);
|
|
||||||
expect(hasClass(".info-value")).toBe(true);
|
|
||||||
expect(hasClass(".inline-badge")).toBe(true);
|
|
||||||
expect(hasClass(".stats-grid")).toBe(true);
|
|
||||||
expect(hasClass(".stat-card")).toBe(true);
|
|
||||||
expect(hasClass(".stat-value")).toBe(true);
|
|
||||||
expect(hasClass(".stat-label")).toBe(true);
|
|
||||||
expect(hasClass(".current-task")).toBe(true);
|
|
||||||
expect(hasClass(".task-badge")).toBe(true);
|
|
||||||
expect(hasClass(".metadata-json")).toBe(true);
|
|
||||||
expect(hasClass(".logs-tab")).toBe(true);
|
|
||||||
expect(hasClass(".logs-header")).toBe(true);
|
|
||||||
expect(hasClass(".logs-count")).toBe(true);
|
|
||||||
expect(hasClass(".streaming-indicator")).toBe(true);
|
|
||||||
expect(hasClass(".streaming-dot")).toBe(true);
|
|
||||||
expect(hasClass(".logs-empty")).toBe(true);
|
|
||||||
expect(hasClass(".runs-tab")).toBe(true);
|
|
||||||
expect(hasClass(".runs-empty")).toBe(true);
|
|
||||||
expect(hasClass(".run-card")).toBe(true);
|
|
||||||
expect(hasClass(".run-card--active")).toBe(true);
|
|
||||||
expect(hasClass(".run-header")).toBe(true);
|
|
||||||
expect(hasClass(".run-live-indicator")).toBe(true);
|
|
||||||
expect(hasClass(".live-dot")).toBe(true);
|
|
||||||
expect(hasClass(".run-id")).toBe(true);
|
|
||||||
expect(hasClass(".run-status")).toBe(true);
|
|
||||||
expect(hasClass(".run-details")).toBe(true);
|
|
||||||
expect(hasClass(".config-tab")).toBe(true);
|
|
||||||
expect(hasClass(".config-section")).toBe(true);
|
|
||||||
expect(hasClass(".config-description")).toBe(true);
|
|
||||||
expect(hasClass(".config-fields")).toBe(true);
|
|
||||||
expect(hasClass(".config-field")).toBe(true);
|
|
||||||
expect(hasClass(".config-hint")).toBe(true);
|
|
||||||
expect(hasClass(".config-error")).toBe(true);
|
|
||||||
expect(hasClass(".config-actions")).toBe(true);
|
|
||||||
expect(hasClass(".config-saved-indicator")).toBe(true);
|
|
||||||
expect(hasClass(".input--error")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should define AgentReflectionsTab and ratings CSS classes", () => {
|
|
||||||
expect(hasClass(".reflections-tab")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-header")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-stats-grid")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-stat-card")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-no-data")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-loading-indicator")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-ratings-section")).toBe(true);
|
|
||||||
expect(hasClass(".reflections-list")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-cards")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-card")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-card--expanded")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-card-header")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-trigger-badge")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-summary")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-details")).toBe(true);
|
|
||||||
expect(hasClass(".reflection-empty")).toBe(true);
|
|
||||||
|
|
||||||
expect(hasClass(".rating-summary-card")).toBe(true);
|
|
||||||
expect(hasClass(".rating-score-display")).toBe(true);
|
|
||||||
expect(hasClass(".rating-average")).toBe(true);
|
|
||||||
expect(hasClass(".rating-stats")).toBe(true);
|
|
||||||
expect(hasClass(".rating-count")).toBe(true);
|
|
||||||
expect(hasClass(".rating-trend-badge")).toBe(true);
|
|
||||||
expect(hasClass(".trend-improving")).toBe(true);
|
|
||||||
expect(hasClass(".trend-declining")).toBe(true);
|
|
||||||
expect(hasClass(".trend-stable")).toBe(true);
|
|
||||||
expect(hasClass(".trend-insufficient")).toBe(true);
|
|
||||||
expect(hasClass(".category-breakdown")).toBe(true);
|
|
||||||
expect(hasClass(".category-item")).toBe(true);
|
|
||||||
expect(hasClass(".category-name")).toBe(true);
|
|
||||||
expect(hasClass(".category-score")).toBe(true);
|
|
||||||
expect(hasClass(".add-rating-form")).toBe(true);
|
|
||||||
expect(hasClass(".add-rating-category-select")).toBe(true);
|
|
||||||
expect(hasClass(".add-rating-comment-input")).toBe(true);
|
|
||||||
expect(hasClass(".star-selector")).toBe(true);
|
|
||||||
expect(hasClass(".star-btn")).toBe(true);
|
|
||||||
expect(hasClass(".rating-stars")).toBe(true);
|
|
||||||
expect(hasClass(".star-filled")).toBe(true);
|
|
||||||
expect(hasClass(".star-empty")).toBe(true);
|
|
||||||
expect(hasClass(".rating-history")).toBe(true);
|
|
||||||
expect(hasClass(".rating-history-item")).toBe(true);
|
|
||||||
expect(hasClass(".rating-item-header")).toBe(true);
|
|
||||||
expect(hasClass(".rating-category-badge")).toBe(true);
|
|
||||||
expect(hasClass(".rating-time")).toBe(true);
|
|
||||||
expect(hasClass(".rating-delete-btn")).toBe(true);
|
|
||||||
expect(hasClass(".rating-comment")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should apply accessible focus/hover styles for merged evaluation cards and actions", () => {
|
it("should apply accessible focus/hover styles for merged evaluation cards and actions", () => {
|
||||||
expect(stylesContent).toContain(".star-btn:focus-visible");
|
expect(stylesContent).toContain(".star-btn:focus-visible");
|
||||||
expect(extractRuleBlock(".star-btn:focus-visible")).toContain("box-shadow: var(--focus-ring-strong)");
|
expect(extractRuleBlock(".star-btn:focus-visible")).toContain("box-shadow: var(--focus-ring-strong)");
|
||||||
@@ -358,49 +137,6 @@ describe("Agent CSS classes", () => {
|
|||||||
expect(stylesContent).toContain("flex-wrap: wrap");
|
expect(stylesContent).toContain("flex-wrap: wrap");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify ActiveAgentsPanel classes
|
|
||||||
it("should define ActiveAgentsPanel CSS classes", () => {
|
|
||||||
expect(hasClass(".active-agents-panel")).toBe(true);
|
|
||||||
expect(hasClass(".active-agents-panel-header")).toBe(true);
|
|
||||||
expect(hasClass(".active-agents-grid")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-header")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-name")).toBe(true);
|
|
||||||
expect(hasClass(".status-dot")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-task")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-transcript")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-empty")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-line")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-card-footer")).toBe(true);
|
|
||||||
expect(hasClass(".live-agent-streaming-dot")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify NewAgentDialog classes
|
|
||||||
it("should define NewAgentDialog CSS classes", () => {
|
|
||||||
expect(hasClass(".agent-dialog-overlay")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-header")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-header-title")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-body")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-footer")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-steps")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-step")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-field")).toBe(true);
|
|
||||||
expect(hasClass(".agent-role-grid")).toBe(true);
|
|
||||||
expect(hasClass(".agent-role-option")).toBe(true);
|
|
||||||
expect(hasClass(".agent-role-option-icon")).toBe(true);
|
|
||||||
expect(hasClass(".agent-role-option-label")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-summary")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-summary-row")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-summary-row-label")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-summary-row-value")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-required")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-optional")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-error")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-info")).toBe(true);
|
|
||||||
expect(hasClass(".agent-dialog-loading")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should give role option buttons a tokenized focus-visible state", () => {
|
it("should give role option buttons a tokenized focus-visible state", () => {
|
||||||
expect(stylesContent).toContain(".agent-role-option:focus-visible");
|
expect(stylesContent).toContain(".agent-role-option:focus-visible");
|
||||||
const roleFocusBlock = extractRuleBlock(".agent-role-option:focus-visible");
|
const roleFocusBlock = extractRuleBlock(".agent-role-option:focus-visible");
|
||||||
@@ -408,10 +144,7 @@ describe("Agent CSS classes", () => {
|
|||||||
expect(roleFocusBlock).toContain("box-shadow: var(--focus-ring-strong)");
|
expect(roleFocusBlock).toContain("box-shadow: var(--focus-ring-strong)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should define shared AgentEmptyState component primitives", () => {
|
it("should keep the create-agent empty-state action copy", () => {
|
||||||
expect(hasClass(".agent-empty-state__icon")).toBe(true);
|
|
||||||
expect(hasClass(".agent-empty-state__title")).toBe(true);
|
|
||||||
expect(hasClass(".agent-empty-state__description")).toBe(true);
|
|
||||||
expect(agentEmptyStateContent).toContain("Create Agent");
|
expect(agentEmptyStateContent).toContain("Create Agent");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const COMPONENTS_DIR = join(APP_DIR, "components");
|
|||||||
|
|
||||||
let cached: string | null = null;
|
let cached: string | null = null;
|
||||||
let stylesCached: string | null = null;
|
let stylesCached: string | null = null;
|
||||||
|
let baseOnlyCached: string | null = null;
|
||||||
|
|
||||||
export function loadStylesCss(): string {
|
export function loadStylesCss(): string {
|
||||||
if (stylesCached !== null) return stylesCached;
|
if (stylesCached !== null) return stylesCached;
|
||||||
@@ -40,6 +41,7 @@ export function loadAllAppCss(): string {
|
|||||||
* against @media overrides that happen to come earlier in the source order.
|
* against @media overrides that happen to come earlier in the source order.
|
||||||
*/
|
*/
|
||||||
export function loadAllAppCssBaseOnly(): string {
|
export function loadAllAppCssBaseOnly(): string {
|
||||||
|
if (baseOnlyCached !== null) return baseOnlyCached;
|
||||||
const src = loadAllAppCss();
|
const src = loadAllAppCss();
|
||||||
// Walk and excise any top-level @<rule> { ... } block (e.g. @media, @supports)
|
// Walk and excise any top-level @<rule> { ... } block (e.g. @media, @supports)
|
||||||
let out = "";
|
let out = "";
|
||||||
@@ -60,5 +62,6 @@ export function loadAllAppCssBaseOnly(): string {
|
|||||||
}
|
}
|
||||||
out += src[i++];
|
out += src[i++];
|
||||||
}
|
}
|
||||||
return out;
|
baseOnlyCached = out;
|
||||||
|
return baseOnlyCached;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
"dev": "pnpm build && pnpm typecheck && pnpm dev:serve",
|
"dev": "pnpm build && pnpm typecheck && pnpm dev:serve",
|
||||||
"dev:serve": "vite dev",
|
"dev:serve": "vite dev",
|
||||||
"test": "vitest run --silent=passed-only --reporter=dot --exclude '**/build-output.test.ts'",
|
"test": "vitest run --silent=passed-only --reporter=dot --exclude '**/build-output.test.ts'",
|
||||||
|
"test:app": "vitest run --project dashboard-app --silent=passed-only --reporter=dot --exclude '**/build-output.test.ts'",
|
||||||
|
"test:api": "vitest run --project dashboard-api --silent=passed-only --reporter=dot",
|
||||||
"test:browser-smoke": "node scripts/browser-layout-smoke.mjs",
|
"test:browser-smoke": "node scripts/browser-layout-smoke.mjs",
|
||||||
"test:build": "vitest run --silent=passed-only --reporter=dot app/__tests__/build-output.test.ts",
|
"test:build": "vitest run --silent=passed-only --reporter=dot app/__tests__/build-output.test.ts",
|
||||||
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.app.json"
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.app.json"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe("Session files endpoint", () => {
|
|||||||
mkdirSync(testWorktree, { recursive: true });
|
mkdirSync(testWorktree, { recursive: true });
|
||||||
|
|
||||||
// Initialize git repo
|
// Initialize git repo
|
||||||
execSync("git init", { cwd: testWorktree });
|
execSync("git init --initial-branch=main", { cwd: testWorktree });
|
||||||
execSync("git config user.email test@test.com", { cwd: testWorktree });
|
execSync("git config user.email test@test.com", { cwd: testWorktree });
|
||||||
execSync("git config user.name Test", { cwd: testWorktree });
|
execSync("git config user.name Test", { cwd: testWorktree });
|
||||||
|
|
||||||
|
|||||||
@@ -716,7 +716,7 @@ describe("POST /api/projects route handler", () => {
|
|||||||
const cloneDestination = join(tempRoot, "cloned-project");
|
const cloneDestination = join(tempRoot, "cloned-project");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execFileSync("git", ["init", "--bare", bareRepo]);
|
execFileSync("git", ["init", "--bare", "--initial-branch=main", bareRepo]);
|
||||||
|
|
||||||
const res = await request(
|
const res = await request(
|
||||||
app,
|
app,
|
||||||
|
|||||||
@@ -307,8 +307,8 @@ function getSharedGitTestRepo(): GitTestRepo {
|
|||||||
const repoDir = join(root, "repo");
|
const repoDir = join(root, "repo");
|
||||||
|
|
||||||
mkdirSync(repoDir, { recursive: true });
|
mkdirSync(repoDir, { recursive: true });
|
||||||
execFileSync("git", ["init", "--bare", remoteDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--bare", "--initial-branch=main", remoteDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["init", repoDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--initial-branch=main", repoDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
||||||
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
||||||
@@ -1485,4 +1485,3 @@ describe("Workspace File Routes", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -307,8 +307,8 @@ function getSharedGitTestRepo(): GitTestRepo {
|
|||||||
const repoDir = join(root, "repo");
|
const repoDir = join(root, "repo");
|
||||||
|
|
||||||
mkdirSync(repoDir, { recursive: true });
|
mkdirSync(repoDir, { recursive: true });
|
||||||
execFileSync("git", ["init", "--bare", remoteDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--bare", "--initial-branch=main", remoteDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["init", repoDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--initial-branch=main", repoDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
||||||
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
||||||
@@ -2029,4 +2029,3 @@ describe("GET /tasks/:id/file-diffs", () => {
|
|||||||
|
|
||||||
// --- Git Management route tests ---
|
// --- Git Management route tests ---
|
||||||
// These are integration tests that run against the actual git repository
|
// These are integration tests that run against the actual git repository
|
||||||
|
|
||||||
|
|||||||
@@ -694,8 +694,8 @@ function getSharedGitTestRepo(): GitTestRepo {
|
|||||||
const repoDir = join(root, "repo");
|
const repoDir = join(root, "repo");
|
||||||
|
|
||||||
mkdirSync(repoDir, { recursive: true });
|
mkdirSync(repoDir, { recursive: true });
|
||||||
execFileSync("git", ["init", "--bare", remoteDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--bare", "--initial-branch=main", remoteDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["init", repoDir], { stdio: "pipe" });
|
execFileSync("git", ["init", "--initial-branch=main", repoDir], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.email", "kb-tests@example.com"], { stdio: "pipe" });
|
||||||
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
execFileSync("git", ["-C", repoDir, "config", "user.name", "KB Tests"], { stdio: "pipe" });
|
||||||
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
writeFileSync(join(repoDir, "README.md"), "# Test Repo\n");
|
||||||
@@ -720,4 +720,3 @@ afterAll(() => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetDiagnosticsSink();
|
resetDiagnosticsSink();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,20 +36,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
// `app/**` is React UI — needs jsdom + CSS. `src/**` is the Express
|
|
||||||
// backend, mostly Node-only logic; running it in node env trims jsdom
|
|
||||||
// env+CSS-include cost. The handful of src tests that genuinely need DOM
|
|
||||||
// opt-in via `// @vitest-environment jsdom`.
|
|
||||||
environment: "node",
|
|
||||||
environmentMatchGlobs: [
|
|
||||||
["app/**", "jsdom"],
|
|
||||||
],
|
|
||||||
// Process CSS imports only for jsdom-based tests that assert on
|
|
||||||
// getComputedStyle. Node-env tests under src/** don't need CSS rules and
|
|
||||||
// skipping the transform there cuts a large slice of total wall time.
|
|
||||||
css: { include: [/app\//] },
|
|
||||||
globals: true,
|
globals: true,
|
||||||
include: ["app/**/*.test.{ts,tsx}", "src/**/*.test.{ts,tsx}"],
|
|
||||||
setupFiles: [
|
setupFiles: [
|
||||||
resolve(__dirname, "../core/src/__test-utils__/vitest-setup.ts"),
|
resolve(__dirname, "../core/src/__test-utils__/vitest-setup.ts"),
|
||||||
"./vitest.setup.ts",
|
"./vitest.setup.ts",
|
||||||
@@ -66,6 +53,28 @@ export default defineConfig({
|
|||||||
// 5s default under workspace-concurrent runs.
|
// 5s default under workspace-concurrent runs.
|
||||||
testTimeout: 15_000,
|
testTimeout: 15_000,
|
||||||
hookTimeout: 15_000,
|
hookTimeout: 15_000,
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
extends: true,
|
||||||
|
test: {
|
||||||
|
name: "dashboard-app",
|
||||||
|
environment: "jsdom",
|
||||||
|
include: ["app/**/*.test.{ts,tsx}"],
|
||||||
|
// Process CSS imports only for jsdom tests that assert on
|
||||||
|
// getComputedStyle. Node API tests do not need CSS transforms.
|
||||||
|
css: { include: [/app\//] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extends: true,
|
||||||
|
test: {
|
||||||
|
name: "dashboard-api",
|
||||||
|
environment: "node",
|
||||||
|
include: ["src/**/*.test.{ts,tsx}"],
|
||||||
|
css: { include: [] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
coverage: {
|
coverage: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
reporter: ["text", "html", "json"],
|
reporter: ["text", "html", "json"],
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const clearDaemonAuthEnv = () => {
|
|||||||
clearDaemonAuthEnv();
|
clearDaemonAuthEnv();
|
||||||
|
|
||||||
const noisyOutputMarkers = [
|
const noisyOutputMarkers = [
|
||||||
|
"ExperimentalWarning: SQLite is an experimental feature",
|
||||||
"Subagent result watcher failed",
|
"Subagent result watcher failed",
|
||||||
"pi-async-subagent-results",
|
"pi-async-subagent-results",
|
||||||
"[pi] createFnAgent called",
|
"[pi] createFnAgent called",
|
||||||
|
|||||||
Reference in New Issue
Block a user