test(generators): add feature generator composition e2e test
The core-package e2e tests cover each generator in isolation. None exercised generators running in sequence — so the feature template shipping without an integrations/cms/index.ts barrel went unnoticed until `gen job` failed against a scaffolded feature. This test scaffolds a feature, then runs `gen job` against it. `gen job` throws at its `<gen:job-tasks>` anchor assertion if the barrel is missing, so the composition gap now fails loudly.
This commit is contained in:
76
turbo/generators/__tests__/feature.e2e.test.ts
Normal file
76
turbo/generators/__tests__/feature.e2e.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { describe, it, expect, onTestFinished } from "vitest";
|
||||
import { mkdtempSync, rmSync, cpSync, existsSync, readFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { execSync } from "node:child_process";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Repo root is 2 levels up from turbo/generators/__tests__
|
||||
const REPO_ROOT = resolve(
|
||||
fileURLToPath(import.meta.url),
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
);
|
||||
|
||||
/**
|
||||
* Generator-composition e2e: scaffold a feature, then run `gen job` against
|
||||
* the freshly-scaffolded feature.
|
||||
*
|
||||
* The `core-package` e2e tests verify each generator's output in isolation
|
||||
* (byte-identical snapshots). This one verifies generators run *in sequence*:
|
||||
* `gen job` asserts the consumer feature's `integrations/cms/index.ts` carries
|
||||
* a `<gen:job-tasks>` anchor, so this test fails loudly if the `feature`
|
||||
* template ever stops emitting that barrel — the regression that shipped once.
|
||||
*/
|
||||
describe("e2e: feature generator composition", () => {
|
||||
it(
|
||||
"scaffolds a feature that `gen job` can extend",
|
||||
{ timeout: 180_000 },
|
||||
() => {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "e2e-feature-"));
|
||||
// Remove the temp clone when the test finishes so sequential runs
|
||||
// don't accumulate and fill disk.
|
||||
onTestFinished(() => rmSync(tmp, { recursive: true, force: true }));
|
||||
cpSync(REPO_ROOT, tmp, {
|
||||
recursive: true,
|
||||
filter: (src) =>
|
||||
!src.includes("node_modules") &&
|
||||
!src.includes(".turbo") &&
|
||||
!src.includes(".pnpm-store"),
|
||||
});
|
||||
|
||||
execSync(`cd ${tmp} && pnpm install --frozen-lockfile=false`, {
|
||||
stdio: "ignore",
|
||||
});
|
||||
execSync(
|
||||
`cd ${tmp} && pnpm turbo gen feature --args demo-feature Widget widgets`,
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
// Regression guard: `gen job` throws at its anchor assertion if the
|
||||
// feature template did not emit src/integrations/cms/index.ts.
|
||||
execSync(
|
||||
`cd ${tmp} && pnpm turbo gen job --args demo-feature send-demo-email void`,
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
|
||||
const featureRoot = join(tmp, "packages", "demo-feature");
|
||||
|
||||
const cmsIndex = join(featureRoot, "src/integrations/cms/index.ts");
|
||||
expect(existsSync(cmsIndex)).toBe(true);
|
||||
expect(readFileSync(cmsIndex, "utf8")).toContain(
|
||||
'export { sendDemoEmailTask } from "./jobs/send-demo-email.task"',
|
||||
);
|
||||
|
||||
expect(
|
||||
existsSync(join(featureRoot, "src/jobs/send-demo-email.job.ts")),
|
||||
).toBe(true);
|
||||
|
||||
const pkgJson = JSON.parse(
|
||||
readFileSync(join(featureRoot, "package.json"), "utf8"),
|
||||
) as { exports: Record<string, string> };
|
||||
expect(pkgJson.exports["./cms"]).toBe("./src/integrations/cms/index.ts");
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user