From f69b53d73e6a603b7d60b6ac726049a9e84d28c5 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Thu, 21 May 2026 11:49:56 +0200 Subject: [PATCH] test(generators): add feature generator composition e2e test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` anchor assertion if the barrel is missing, so the composition gap now fails loudly. --- .../generators/__tests__/feature.e2e.test.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 turbo/generators/__tests__/feature.e2e.test.ts diff --git a/turbo/generators/__tests__/feature.e2e.test.ts b/turbo/generators/__tests__/feature.e2e.test.ts new file mode 100644 index 0000000..4872282 --- /dev/null +++ b/turbo/generators/__tests__/feature.e2e.test.ts @@ -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 `` 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 }; + expect(pkgJson.exports["./cms"]).toBe("./src/integrations/cms/index.ts"); + }, + ); +});