open-design/apps/web/tests/components/auto-open-file.test.ts
PerishFire bbdd4e84b5
chore: enforce test directory conventions (#496)
* chore: enforce test directory conventions

Move package, app, and tool tests out of src and add guard enforcement so source directories stay source-only.

* ci: use guard and package-scoped tests

Run the new repository guard in CI and keep test execution aligned with package-scoped commands after removing root aliases.

* ci: align stable release guard check

Use the new repository guard in stable release verification after replacing the residual-JS-only script.

* chore: tighten test layout enforcement

Enforce sibling tests directories, typecheck moved test suites with dedicated configs, and refresh remaining guidance that pointed at src-based tests.

* chore: clarify no-emit test tsconfigs

Explicitly disable declaration-only emit in test tsconfigs so review tooling sees they are no-emit typecheck configs.
2026-05-05 15:34:22 +08:00

101 lines
4.4 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { decideAutoOpenAfterWrite } from '../../src/components/auto-open-file';
describe('decideAutoOpenAfterWrite', () => {
it('returns shouldOpen=false when filePath is empty', () => {
const result = decideAutoOpenAfterWrite('', [{ name: 'index.html' }]);
expect(result).toEqual({ shouldOpen: false, fileName: null });
});
it('returns shouldOpen=true when filePath equals a project file path', () => {
const result = decideAutoOpenAfterWrite('index.html', [
{ name: 'index.html', path: 'index.html' },
{ name: 'styles.css', path: 'styles.css' },
]);
expect(result).toEqual({ shouldOpen: true, fileName: 'index.html' });
});
it('returns shouldOpen=false when filePath has slashes but matches no project path', () => {
// Regression: this is the "rogue empty tab" case — the agent edited a
// file outside the project (e.g. an upstream repo's source file) and
// we must NOT open a placeholder tab for it. filePath has a slash, so
// the basename fallback is intentionally skipped.
const result = decideAutoOpenAfterWrite(
'/home/bryan/projects/open-design/apps/daemon/src/project-watchers.ts',
[
{ name: 'index.html', path: 'index.html' },
{ name: 'App.jsx', path: 'App.jsx' },
],
);
expect(result).toEqual({ shouldOpen: false, fileName: null });
});
it('falls back to basename match when filePath is just a basename', () => {
const result = decideAutoOpenAfterWrite('App.jsx', [
{ name: 'index.html', path: 'index.html' },
{ name: 'App.jsx', path: 'App.jsx' },
{ name: 'styles.css', path: 'styles.css' },
{ name: 'README.md', path: 'README.md' },
]);
expect(result).toEqual({ shouldOpen: true, fileName: 'App.jsx' });
});
it('matches an absolute filePath via path-suffix against a nested project file', () => {
// Real-world case: the agent passes an absolute file_path; the project
// file lives at "prototype/App.jsx". The decision must still resolve
// unambiguously, returning the project-relative file name.
const result = decideAutoOpenAfterWrite(
'/home/bryan/projects/open-design/.od/projects/abc/prototype/App.jsx',
[
{ name: 'index.html', path: 'index.html' },
{ name: 'prototype/App.jsx', path: 'prototype/App.jsx' },
],
);
expect(result).toEqual({ shouldOpen: true, fileName: 'prototype/App.jsx' });
});
it('declines when an absolute filePath could match multiple nested project files (ambiguous)', () => {
// Two project files share the basename "App.jsx" but live in different
// subdirs. The agent's filePath ends with "/App.jsx" only, with no
// disambiguating subdirectory match — refuse rather than open the wrong file.
const result = decideAutoOpenAfterWrite(
'/some/external/path/App.jsx',
[
{ name: 'src/App.jsx', path: 'src/App.jsx' },
{ name: 'lib/App.jsx', path: 'lib/App.jsx' },
],
);
expect(result).toEqual({ shouldOpen: false, fileName: null });
});
it('declines when filePath has a slash and no project path is a suffix match', () => {
// Agent edited /upstream/repo/App.jsx; project also has prototype/App.jsx.
// The previous (basename-only) implementation would have opened the
// wrong file; the path-suffix check leaves zero matches and the
// basename fallback is intentionally skipped because filePath has a slash.
const result = decideAutoOpenAfterWrite('/upstream/repo/App.jsx', [
{ name: 'prototype/App.jsx', path: 'prototype/App.jsx' },
]);
expect(result).toEqual({ shouldOpen: false, fileName: null });
});
it('still works when ProjectFile entries omit the optional path field', () => {
// Defensive: ProjectFile.path is optional in the API contract. Fall
// back to using `name` (which the daemon populates with the full
// project-relative path) when path is missing.
const result = decideAutoOpenAfterWrite('index.html', [
{ name: 'index.html' },
{ name: 'styles.css' },
]);
expect(result).toEqual({ shouldOpen: true, fileName: 'index.html' });
});
it('declines a basename fallback when multiple project files share the basename', () => {
const result = decideAutoOpenAfterWrite('App.jsx', [
{ name: 'src/App.jsx', path: 'src/App.jsx' },
{ name: 'lib/App.jsx', path: 'lib/App.jsx' },
]);
expect(result).toEqual({ shouldOpen: false, fileName: null });
});
});