/** * Activation checklist unit tests (node:test). * Plan-gated optional store-connect + workspace cursor (no $lib / i18n). * * Run from apps/web: * node --experimental-strip-types --test src/lib/activation.test.ts */ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { activationIndexFromWorkspace, visibleActivationSteps, type ActivationStepRef } from "./activation/workspace.ts"; const FULL_STEPS: ActivationStepRef[] = [ { id: "enable-fields" }, { id: "connect-source" }, { id: "map" }, { id: "sync-sample" }, { id: "process" }, { id: "store-connect", optional: true, feature: "stores.hub" }, { id: "export" } ]; describe("visibleActivationSteps", () => { it("includes store-connect when stores.hub is allowed", () => { const steps = visibleActivationSteps(FULL_STEPS, () => true); assert.ok(steps.some((s) => s.id === "store-connect")); assert.equal(steps.find((s) => s.id === "store-connect")?.optional, true); assert.equal(steps.find((s) => s.id === "store-connect")?.feature, "stores.hub"); }); it("hides store-connect when stores.hub is denied (A1-safe)", () => { const steps = visibleActivationSteps(FULL_STEPS, (key) => key !== "stores.hub"); assert.equal( steps.some((s) => s.id === "store-connect"), false ); assert.deepEqual( steps.map((s) => s.id), ["enable-fields", "connect-source", "map", "sync-sample", "process", "export"] ); }); it("places store-connect after process and before export", () => { const ids = FULL_STEPS.map((s) => s.id); assert.equal(ids.indexOf("store-connect"), ids.indexOf("process") + 1); assert.equal(ids.indexOf("export"), ids.indexOf("store-connect") + 1); }); }); describe("activationIndexFromWorkspace", () => { const throughProcess = { hasEnabledFields: true, hasSource: true, hasMapping: true, hasSyncedSample: true, hasProcessed: true }; it("stops on optional store-connect when no store and no later required evidence", () => { const steps = visibleActivationSteps(FULL_STEPS, () => true); const count = activationIndexFromWorkspace(throughProcess, steps); assert.equal(count, steps.findIndex((s) => s.id === "store-connect")); }); it("does not block export when optional store is incomplete", () => { const steps = visibleActivationSteps(FULL_STEPS, () => true); const count = activationIndexFromWorkspace( { ...throughProcess, hasExport: true }, steps ); assert.equal(count, steps.findIndex((s) => s.id === "export") + 1); }); it("advances past store-connect when a store is connected", () => { const steps = visibleActivationSteps(FULL_STEPS, () => true); const count = activationIndexFromWorkspace( { ...throughProcess, hasStoreConnect: true }, steps ); assert.equal(count, steps.findIndex((s) => s.id === "export")); }); it("skips store evidence when step is gated out", () => { const steps = visibleActivationSteps(FULL_STEPS, (key) => key !== "stores.hub"); const count = activationIndexFromWorkspace( { ...throughProcess, hasExport: true }, steps ); assert.equal(count, steps.length); }); }); describe("activation Continue destinations", () => { /** Mirror of ACTIVATION_STEP_DEFS hrefs — keep in sync with activation/steps.ts. */ const CONTINUE_HREFS: Record = { "enable-fields": "/standard-fields", "connect-source": "/feeds?add=1", map: "/feeds?focus=map", "sync-sample": "/feeds?focus=sync", process: "/products?type=raw&status=unprocessed", "store-connect": "/stores/wizard", export: "/export-feeds" }; it("keeps feed-first connect-source away from the stores wizard", () => { assert.match(CONTINUE_HREFS["connect-source"], /^\/feeds/); assert.doesNotMatch(CONTINUE_HREFS["connect-source"], /stores/); assert.equal(CONTINUE_HREFS["store-connect"], "/stores/wizard"); }); it("matches the checklist sequence destinations", () => { assert.deepEqual( FULL_STEPS.map((s) => CONTINUE_HREFS[s.id]), [ "/standard-fields", "/feeds?add=1", "/feeds?focus=map", "/feeds?focus=sync", "/products?type=raw&status=unprocessed", "/stores/wizard", "/export-feeds" ] ); }); });