refactor(test): trim development script coverage

This commit is contained in:
murashit 2026-07-17 08:26:37 +09:00
parent 40153a1da5
commit dfff322541
2 changed files with 58 additions and 286 deletions

View file

@ -14,17 +14,6 @@ afterEach(async () => {
});
describe("development scripts", () => {
it.each([
{ message: "feat: add side chats", expectedStatus: 0 },
{ message: "Merge pull request #123 from owner/feature", expectedStatus: 0 },
{ message: 'Revert "feat: add side chats"', expectedStatus: 1 },
{ message: "v5.1.0", expectedStatus: 1 },
{ message: "fixup! feat: add side chats", expectedStatus: 1 },
{ message: "squash! feat: add side chats", expectedStatus: 1 },
])("enforces the commit message policy for $message", ({ message, expectedStatus }) => {
expect(runCommitlint(message)).toBe(expectedStatus);
});
it("fails style builds when CSS files are missing from the style order file", async () => {
const cwd = await styleOrderFixture();
@ -66,28 +55,6 @@ describe("development scripts", () => {
);
});
it("preserves generated bindings when generation fails", async () => {
const cwd = await tempWorkspace();
const generatedDir = path.join(cwd, "src", "generated", "app-server");
await mkdir(generatedDir, { recursive: true });
await writeFile(path.join(generatedDir, "Existing.ts"), "export type Existing = true;\n");
await writeAppServerCompatibility(cwd, "0.144.4");
const { generateAppServerTypes } = await import(pathToFileURL(path.join(repoRoot, "scripts", "generate-app-server-types.mjs")).href);
await expect(
generateAppServerTypes({
cwd,
readCodexVersion: () => "0.144.4",
runCommand: async () => {
throw new Error("generation failed");
},
}),
).rejects.toThrow("generation failed");
await expect(readFile(path.join(generatedDir, "Existing.ts"), "utf8")).resolves.toBe("export type Existing = true;\n");
await expect(readdir(path.join(cwd, "src", "generated"))).resolves.toEqual(["app-server"]);
});
it("checks normalized generated bindings without replacing the tracked tree", async () => {
const cwd = await tempWorkspace();
const generatedDir = path.join(cwd, "src", "generated", "app-server");
@ -141,85 +108,6 @@ describe("development scripts", () => {
);
});
it("reads app-server compatibility policy from the declared baseline in API baseline checks", async () => {
const cwd = await tempWorkspace();
await mkdir(path.join(cwd, "src", "app-server"), { recursive: true });
const { createApiBaselineReport } = await import(pathToFileURL(path.join(repoRoot, "scripts", "api-baseline.mjs")).href);
await writeJson(path.join(cwd, "package.json"), {
version: "1.0.0",
devDependencies: {
obsidian: "~1.12.3",
},
});
await writeJson(path.join(cwd, "package-lock.json"), {
packages: {
"node_modules/obsidian": {
version: "1.12.3",
},
},
});
await writeJson(path.join(cwd, "manifest.json"), {
minAppVersion: "1.12.0",
});
await writeJson(path.join(cwd, "versions.json"), {
"1.0.0": "1.12.0",
});
await writeFile(
path.join(cwd, "README.md"),
[
"## Compatibility",
"",
"| Key | Version | Notes |",
"| --- | --- | --- |",
"| `codex.testedCliVersion` | `0.139.0` | Tested CLI. |",
"| `manifest.minAppVersion` | `1.12.0` | Minimum app version. |",
"| `obsidian` API types | `1.12.3` | Compile-time API package. |",
].join("\n"),
);
await mkdir(path.join(cwd, "src", "app-server", "connection"), { recursive: true });
await writeJson(path.join(cwd, "src", "app-server", "connection", "compatibility.json"), {
codexAppServer: {
testedCliVersion: "0.139.0",
typeGeneration: {
arguments: ["app-server", "generate-ts", "--experimental"],
},
initialize: {
capabilities: {
experimentalApi: true,
requestAttestation: false,
},
},
},
});
const report = await createApiBaselineReport({
cwd,
readCodexVersion: () => "0.139.0",
});
expect(report.codex.recordedTestedCliVersion).toBe("0.139.0");
expect(report.codex.generationArguments).toEqual(["app-server", "generate-ts", "--experimental"]);
expect(report.codex.readmeMatchesRecordedVersion).toBe(true);
expect(report.codex.localCliMatchesRecordedVersion).toBe(true);
expect(report.codex.initializeExperimentalApi).toBe(true);
expect(report.codex.initializeRequestAttestationDisabled).toBe(true);
expect(report.failures).toEqual([]);
const missingLocalCliReport = await createApiBaselineReport({
cwd,
readCodexVersion: () => null,
});
expect(missingLocalCliReport.failures).toContain("local codex --version could not be read.");
});
it("rejects the obsolete recorded-only API baseline mode", () => {
const result = runNodeScript("scripts/api-baseline.mjs", ["--recorded-only"], repoRoot);
expect(result.status).toBe(1);
expect(result.stderr).toContain("Usage: node scripts/api-baseline.mjs [--json]");
});
it("reports app-server provenance drift between the recorded, README, and local CLI versions", async () => {
const cwd = await apiBaselineFixture({ recordedCodexVersion: "0.144.4", readmeCodexVersion: "0.144.0" });
const { createApiBaselineReport } = await import(pathToFileURL(path.join(repoRoot, "scripts", "api-baseline.mjs")).href);
@ -230,30 +118,28 @@ describe("development scripts", () => {
expect(report.failures).toContain("local Codex CLI 0.144.5 does not match recorded tested CLI 0.144.4.");
});
it("checks generated app-server bindings in CI and release paths", async () => {
const [checkWorkflow, releaseWorkflow, releasePreflight] = await Promise.all([
readFile(path.join(repoRoot, ".github", "workflows", "check.yml"), "utf8"),
readFile(path.join(repoRoot, ".github", "workflows", "release.yml"), "utf8"),
readFile(path.join(repoRoot, "scripts", "release", "preflight.mjs"), "utf8"),
]);
for (const workflow of [checkWorkflow, releaseWorkflow]) {
expect(workflow).toContain("npm install --global");
expect(workflow).toContain("node scripts/app-server-compatibility.mjs --tested-cli-version");
expect(workflow).toContain("npm run generate:app-server-types:check");
}
expect(releasePreflight).toContain('run("npm", ["run", "generate:app-server-types:check"]);');
});
it("reports representative CSS usage policy failures", async () => {
const cwd = await cssUsageFixture({
"src/styles/10-component.css": [
".codex-panel__used { display: block; }",
".codex-panel__test-only { display: block; }",
".codex-panel__unused { display: block; }",
".codex-panel__item { display: block; }",
".codex-panel__item-detail { display: block; }",
".codex-panel__tokens {",
" --codex-panel-used-size: 1px;",
" --codex-panel-unused-size: 2px;",
" width: var(--codex-panel-used-size);",
" /*",
" --codex-panel-commented-size: 3px;",
" width: var(--codex-panel-unused-size);",
" */",
"}",
].join("\n"),
"src/component.ts": [
'export const className = "codex-panel__used";',
'export const detailClassName = "codex-panel__item-detail";',
'export const tokenClassName = "codex-panel__tokens";',
"export const dynamicClassName = `codex-panel__task-step--$" + "{status}`;",
].join("\n"),
"tests/component.test.ts": 'expect("codex-panel__test-only").toBeTruthy();\n',
@ -268,98 +154,11 @@ describe("development scripts", () => {
expect(result.stderr).toContain("codex-panel__task-step--");
expect(result.stderr).toContain("codex-panel__test-only");
expect(result.stderr).toContain("codex-panel__unused");
});
it("reports unused panel custom properties", async () => {
const cwd = await cssUsageFixture({
"src/styles/10-component.css": [
".codex-panel__used {",
" --codex-panel-used-size: 1px;",
" --codex-panel-unused-size: 2px;",
" width: var(--codex-panel-used-size);",
" /*",
" --codex-panel-commented-size: 3px;",
" width: var(--codex-panel-unused-size);",
" */",
"}",
].join("\n"),
"src/component.ts": 'export const className = "codex-panel__used";\n',
});
const result = runNodeScript("scripts/check-css-usage.mjs", [], cwd);
expect(result.status).toBe(1);
expect(result.stderr).toContain("Unused CSS custom property candidates:");
expect(result.stderr).toContain(" codex-panel__item\n");
expect(result.stderr).toContain("--codex-panel-unused-size");
expect(result.stderr).not.toContain("--codex-panel-used-size\n");
expect(result.stderr).not.toContain("--codex-panel-commented-size");
});
it("does not count a longer source class name as usage", async () => {
const cwd = await cssUsageFixture({
"src/styles/10-component.css": [".codex-panel__item { display: block; }", ".codex-panel__item-detail { display: block; }"].join("\n"),
"src/component.ts": 'export const className = "codex-panel__item-detail";\n',
});
const result = runNodeScript("scripts/check-css-usage.mjs", [], cwd);
expect(result.status).toBe(1);
expect(result.stderr).toContain("Unused CSS class candidates:");
expect(result.stderr).toContain(" codex-panel__item\n");
});
it("prepares release notes from conventional commits since the previous tag", async () => {
const cwd = await tempWorkspace();
await writeJson(path.join(cwd, "package.json"), { version: "2.3.2" });
await writeJson(path.join(cwd, "package-lock.json"), {
version: "2.3.2",
packages: { "": { version: "2.3.2" } },
});
await writeJson(path.join(cwd, "manifest.json"), { version: "2.3.2", minAppVersion: "1.12.0" });
await writeJson(path.join(cwd, "versions.json"), { "2.3.2": "1.12.0" });
runGit(["init"], cwd);
runGit(["config", "user.name", "Codex Panel Tests"], cwd);
runGit(["config", "user.email", "tests@example.com"], cwd);
runGit(["add", "."], cwd);
runGit(["commit", "-m", "chore: establish release baseline"], cwd);
runGit(["tag", "2.3.2"], cwd);
await writeFile(path.join(cwd, "change.txt"), "side chats\n");
runGit(["add", "change.txt"], cwd);
runGit(["commit", "-m", "feat(chat): add side chat support"], cwd);
const result = runNodeScript("scripts/release/prepare.mjs", ["2.4.0"], cwd);
expect(result.status).toBe(0);
await expect(readFile(path.join(cwd, ".github", "release-notes", "2.4.0.md"), "utf8")).resolves.toBe(
"## Changes\n\n- Add side chat support.\n",
);
await expect(readJson(path.join(cwd, "package.json"))).resolves.toMatchObject({ version: "2.4.0" });
});
it("fails release prepare before changing version files when release notes already exist", async () => {
const cwd = await tempWorkspace();
await mkdir(path.join(cwd, ".github", "release-notes"), { recursive: true });
const packageJson = { version: "2.3.2" };
const packageLockJson = { version: "2.3.2", packages: { "": { version: "2.3.2" } } };
const manifestJson = { version: "2.3.2", minAppVersion: "1.12.0" };
const versionsJson = { "2.3.2": "1.12.0" };
await writeJson(path.join(cwd, "package.json"), packageJson);
await writeJson(path.join(cwd, "package-lock.json"), packageLockJson);
await writeJson(path.join(cwd, "manifest.json"), manifestJson);
await writeJson(path.join(cwd, "versions.json"), versionsJson);
await writeFile(path.join(cwd, ".github", "release-notes", "2.3.3.md"), "## Changes\n\n- Existing\n");
const result = runNodeScript("scripts/release/prepare.mjs", ["2.3.3"], cwd);
expect(result.status).toBe(1);
expect(result.stderr).toContain(".github/release-notes/2.3.3.md already exists");
await expect(readJson(path.join(cwd, "package.json"))).resolves.toEqual(packageJson);
await expect(readJson(path.join(cwd, "package-lock.json"))).resolves.toEqual(packageLockJson);
await expect(readJson(path.join(cwd, "manifest.json"))).resolves.toEqual(manifestJson);
await expect(readJson(path.join(cwd, "versions.json"))).resolves.toEqual(versionsJson);
});
});
async function tempWorkspace(): Promise<string> {
@ -446,24 +245,6 @@ function runNodeScript(script: string, args: string[] = [], cwd = repoRoot, env:
});
}
function runCommitlint(message: string): number | null {
return spawnSync(process.execPath, [path.join(repoRoot, "node_modules", "@commitlint", "cli", "cli.js")], {
cwd: repoRoot,
encoding: "utf8",
input: `${message}\n`,
shell: false,
}).status;
}
function runGit(args: string[], cwd: string): void {
const result = spawnSync("git", args, { cwd, encoding: "utf8", shell: false });
if (result.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr}`);
}
async function writeJson(file: string, value: unknown): Promise<void> {
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
}
async function readJson(file: string): Promise<unknown> {
return JSON.parse(await readFile(file, "utf8"));
}

View file

@ -173,7 +173,15 @@ const policyCases = [
'import { renderPreactRoot } from "../../../shared/dom/preact-root.dom";',
"export const value = 1;",
),
policyCase("no-restricted-css-policy.grit", "src/styles/escape.css", ".escape { color: #fff; }", ".safe { color: var(--text-normal); }"),
policyCase("no-restricted-css-policy.grit", "src/styles/escape.css", ".escape { color: #fff; }", ".safe { color: var(--text-normal); }", {
invalid: [
{ path: "src/styles/has.css", source: ".panel:has(.child) { color: var(--text-normal); }" },
{ path: "src/styles/where.css", source: ".panel:where(.child) { color: var(--text-normal); }" },
{ path: "src/styles/id.css", source: "#panel { color: var(--text-normal); }" },
{ path: "src/styles/universal.css", source: ".panel * { color: var(--text-normal); }" },
],
valid: [{ path: "src/styles/shallow.css", source: ".panel .child:hover { color: var(--text-normal); }" }],
}),
];
afterEach(async () => {
@ -191,70 +199,46 @@ describe("Biome Grit policies", () => {
expect(covered).toEqual(configured);
});
it.each(policyCases)("$plugin rejects its invalid source", async (testCase) => {
const result = await lintPolicyCase(testCase, "invalid");
it.each(policyCases)("$plugin rejects invalid sources and accepts valid sources", async (testCase) => {
const result = await lintPolicyCase(testCase);
expect(result.status, result.output).toBe(1);
expect(result.pluginErrors, result.output).toBeGreaterThan(0);
});
it.each(policyCases)("$plugin accepts its valid source", async (testCase) => {
const result = await lintPolicyCase(testCase, "valid");
expect(result.status, result.output).toBe(0);
expect(result.pluginErrors, result.output).toBe(0);
});
it.each([
["the relational :has pseudo-class", ".panel:has(.child) { color: var(--text-normal); }"],
["class selectors hidden inside :where", ".panel:where(.child) { color: var(--text-normal); }"],
["ID selectors", "#panel { color: var(--text-normal); }"],
["universal selectors", ".panel * { color: var(--text-normal); }"],
])("no-restricted-css-policy.grit rejects %s", async (_name, invalidSource) => {
const result = await lintPolicyCase(
{
plugin: "no-restricted-css-policy.grit",
invalid: { path: "src/styles/invalid.css", source: invalidSource },
},
"invalid",
);
expect(result.status, result.output).toBe(1);
expect(result.pluginErrors, result.output).toBeGreaterThan(0);
});
it("no-restricted-css-policy.grit accepts shallow panel selectors", async () => {
const result = await lintPolicyCase(
{
plugin: "no-restricted-css-policy.grit",
valid: { path: "src/styles/valid.css", source: ".panel .child:hover { color: var(--text-normal); }" },
},
"valid",
);
expect(result.status, result.output).toBe(0);
expect(result.pluginErrors, result.output).toBe(0);
for (const target of result.invalidTargets) expect(result.pluginErrorFiles, result.output).toContain(target);
for (const target of result.validTargets) expect(result.errorFiles, result.output).not.toContain(target);
});
});
function policyCase(plugin, invalidPath, invalidSource, validSource, options = {}) {
return {
plugin,
invalid: { path: invalidPath, source: invalidSource },
valid: { path: options.validPath ?? invalidPath, source: validSource },
invalid: [{ path: invalidPath, source: invalidSource }, ...(options.invalid ?? [])],
valid: [{ path: options.validPath ?? invalidPath, source: validSource }, ...(options.valid ?? [])],
};
}
async function lintPolicyCase(testCase, variant) {
async function lintPolicyCase(testCase) {
const plugin = (await projectPlugins()).find((candidate) => path.basename(pluginPath(candidate)) === testCase.plugin);
if (!plugin) throw new Error(`Missing configured Grit policy: ${testCase.plugin}`);
const workspace = await mkdtemp(path.join(tmpdir(), "codex-panel-grit-policy-"));
workspaces.add(workspace);
const fixture = testCase[variant];
const fixturePath = path.join(workspace, fixture.path);
await mkdir(path.dirname(fixturePath), { recursive: true });
await Promise.all([writeFile(fixturePath, fixture.source), writePluginConfig(workspace, plugin)]);
return biomeLint(workspace, fixture.path);
const [invalidTargets, validTargets] = await Promise.all([
writeFixtures(workspace, "invalid", testCase.invalid),
writeFixtures(workspace, "valid", testCase.valid),
writePluginConfig(workspace, plugin),
]);
return { ...biomeLint(workspace, [...invalidTargets, ...validTargets]), invalidTargets, validTargets };
}
async function writeFixtures(workspace, variant, fixtures) {
return Promise.all(
fixtures.map(async (fixture) => {
const target = path.join(variant, fixture.path).replaceAll(path.sep, "/");
const fixturePath = path.join(workspace, target);
await mkdir(path.dirname(fixturePath), { recursive: true });
await writeFile(fixturePath, fixture.source);
return target;
}),
);
}
async function projectPlugins() {
@ -271,14 +255,21 @@ async function writePluginConfig(workspace, plugin) {
await writeFile(path.join(workspace, "biome.json"), JSON.stringify(config));
}
function biomeLint(workspace, target) {
const result = spawnSync(biomeBin, ["lint", target, "--config-path", workspace, "--reporter=json", "--max-diagnostics=none"], {
function biomeLint(workspace, targets) {
const result = spawnSync(biomeBin, ["lint", ...targets, "--config-path", workspace, "--reporter=json", "--max-diagnostics=none"], {
cwd: workspace,
encoding: "utf8",
});
const report = JSON.parse(result.stdout.slice(result.stdout.indexOf("{")));
const errorDiagnostics = report.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
const diagnosticFiles = (diagnostics) =>
diagnostics
.map((diagnostic) => (typeof diagnostic.location?.path === "string" ? diagnostic.location.path : diagnostic.location?.path?.file))
.filter(Boolean)
.map((file) => path.relative(workspace, path.resolve(workspace, file)).replaceAll(path.sep, "/"));
return {
pluginErrors: report.diagnostics.filter((diagnostic) => diagnostic.category === "plugin" && diagnostic.severity === "error").length,
errorFiles: diagnosticFiles(errorDiagnostics),
pluginErrorFiles: diagnosticFiles(errorDiagnostics.filter((diagnostic) => diagnostic.category === "plugin")),
status: result.status,
output: `${result.stdout}\n${result.stderr}`,
};