fix(todos): restore clean build and navigation coverage (#3321)
## Summary - remove an unavailable jest-dom type from the Todo plugin production TypeScript build - update the dashboard navigation fixture for the plugin-owned Todo destination and root test id ## Test plan - `corepack pnpm --filter @fusion-plugin-examples/todos build` - `corepack pnpm --filter @fusion-plugin-examples/todos test` - `FUSION_DASHBOARD_DEEP=1 corepack pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/navigation-history.test.tsx --project dashboard-app-quality-components-a --silent=passed-only --reporter=dot` - `corepack pnpm check:changesets` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Restored CLI packaging for the bundled Todo Lists plugin. * Made `AgentStore` available to bundled plugins at runtime. * **Tests** * Updated navigation coverage for Todo Lists dashboard views, overflow placement, and ordering. * Added coverage for opening and dismissing the Todo view through browser history navigation. * Improved validation of runtime exports. * **Chores** * Simplified test type configuration for the Todo Lists plugin. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
7
.changeset/calm-todos-build.md
Normal file
7
.changeset/calm-todos-build.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Restore clean CLI packaging for the bundled Todo Lists plugin.
|
||||||
|
category: fix
|
||||||
|
dev: Re-exports AgentStore through the bundled plugin core runtime shim.
|
||||||
@@ -1,11 +1,39 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
import ts from "typescript";
|
||||||
import { definePlugin, validatePluginManifest } from "@fusion/plugin-sdk";
|
import { definePlugin, validatePluginManifest } from "@fusion/plugin-sdk";
|
||||||
import { applyPrepackTransform } from "../../scripts/prepare-publish-manifest.mjs";
|
import { applyPrepackTransform } from "../../scripts/prepare-publish-manifest.mjs";
|
||||||
|
|
||||||
const workspaceRoot = join(__dirname, "..", "..", "..", "..");
|
const workspaceRoot = join(__dirname, "..", "..", "..", "..");
|
||||||
|
|
||||||
|
function executableModuleSpecifiers(source: string): string[] {
|
||||||
|
const sourceFile = ts.createSourceFile("artifact.js", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
||||||
|
const specifiers: string[] = [];
|
||||||
|
|
||||||
|
const visit = (node: ts.Node): void => {
|
||||||
|
if (
|
||||||
|
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
|
||||||
|
node.moduleSpecifier &&
|
||||||
|
ts.isStringLiteralLike(node.moduleSpecifier)
|
||||||
|
) {
|
||||||
|
specifiers.push(node.moduleSpecifier.text);
|
||||||
|
} else if (
|
||||||
|
ts.isCallExpression(node) &&
|
||||||
|
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
||||||
|
(ts.isIdentifier(node.expression) && node.expression.text === "require")) &&
|
||||||
|
node.arguments.length === 1 &&
|
||||||
|
ts.isStringLiteralLike(node.arguments[0])
|
||||||
|
) {
|
||||||
|
specifiers.push(node.arguments[0].text);
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit);
|
||||||
|
};
|
||||||
|
|
||||||
|
visit(sourceFile);
|
||||||
|
return specifiers;
|
||||||
|
}
|
||||||
|
|
||||||
describe("plugin-sdk export surface", () => {
|
describe("plugin-sdk export surface", () => {
|
||||||
it("keeps definePlugin as identity and validates manifests", () => {
|
it("keeps definePlugin as identity and validates manifests", () => {
|
||||||
const plugin = { manifest: { id: "demo-plugin", name: "Demo", version: "1.0.0" } } as any;
|
const plugin = { manifest: { id: "demo-plugin", name: "Demo", version: "1.0.0" } } as any;
|
||||||
@@ -43,7 +71,7 @@ describe("plugin-sdk export surface", () => {
|
|||||||
expect(tsupRaw).toContain("/^@fusion\\//");
|
expect(tsupRaw).toContain("/^@fusion\\//");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses a runtime-only core shim that bundles schema source and Quality supervision without core dist", () => {
|
it("uses a runtime-only core shim that bundles required plugin values without core dist", () => {
|
||||||
const tsupPath = join(workspaceRoot, "packages", "cli", "tsup.config.ts");
|
const tsupPath = join(workspaceRoot, "packages", "cli", "tsup.config.ts");
|
||||||
const tsupRaw = readFileSync(tsupPath, "utf-8");
|
const tsupRaw = readFileSync(tsupPath, "utf-8");
|
||||||
const shimPath = join(workspaceRoot, "packages", "cli", "src", "plugin-sdk-core-runtime-shim.mjs");
|
const shimPath = join(workspaceRoot, "packages", "cli", "src", "plugin-sdk-core-runtime-shim.mjs");
|
||||||
@@ -51,6 +79,12 @@ describe("plugin-sdk export surface", () => {
|
|||||||
|
|
||||||
expect(tsupRaw).toContain('"plugin-sdk-core-runtime-shim.mjs"');
|
expect(tsupRaw).toContain('"plugin-sdk-core-runtime-shim.mjs"');
|
||||||
expect(shimRaw).toContain('from "../../core/src/postgres/schema/index.js"');
|
expect(shimRaw).toContain('from "../../core/src/postgres/schema/index.js"');
|
||||||
|
/*
|
||||||
|
* FNXC:BundledPlugins 2026-08-03-18:39:
|
||||||
|
* Bundled plugins must receive runtime values through the CLI source shim so clean package builds never leave private `@fusion/core` imports unresolved.
|
||||||
|
*/
|
||||||
|
expect(shimRaw).toContain('from "../../core/src/agents/agent-store.js"');
|
||||||
|
expect(shimRaw).toContain("export { AgentStore, postgresSchema }");
|
||||||
expect(shimRaw).toContain("export function superviseSpawn");
|
expect(shimRaw).toContain("export function superviseSpawn");
|
||||||
expect(shimRaw).not.toContain("../../core/dist/");
|
expect(shimRaw).not.toContain("../../core/dist/");
|
||||||
});
|
});
|
||||||
@@ -61,7 +95,27 @@ describe("plugin-sdk export surface", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const built = readFileSync(distPath, "utf-8");
|
const built = readFileSync(distPath, "utf-8");
|
||||||
expect(built.includes("@fusion/")).toBe(false);
|
const fusionRuntimeSpecifiers = executableModuleSpecifiers(built).filter((specifier) =>
|
||||||
|
specifier.startsWith("@fusion/"),
|
||||||
|
);
|
||||||
|
expect(fusionRuntimeSpecifiers).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("distinguishes executable @fusion specifiers from documentation text", () => {
|
||||||
|
const source = [
|
||||||
|
'import value from "@fusion/static";',
|
||||||
|
'import /* comment */ ("@fusion/dynamic");',
|
||||||
|
'require(/* comment */ "@fusion/commonjs");',
|
||||||
|
'export {} from /* comment */ "@fusion/exported";',
|
||||||
|
'const docs = "Run pnpm --filter @fusion/core test";',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
expect(executableModuleSpecifiers(source)).toEqual([
|
||||||
|
"@fusion/static",
|
||||||
|
"@fusion/dynamic",
|
||||||
|
"@fusion/commonjs",
|
||||||
|
"@fusion/exported",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has no @fusion specifiers in built plugin-sdk declaration artifact when present", () => {
|
it("has no @fusion specifiers in built plugin-sdk declaration artifact when present", () => {
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ import { spawn } from "node:child_process";
|
|||||||
* and bundles every runtime export without a private @fusion/core dependency.
|
* and bundles every runtime export without a private @fusion/core dependency.
|
||||||
*/
|
*/
|
||||||
import * as postgresSchema from "../../core/src/postgres/schema/index.js";
|
import * as postgresSchema from "../../core/src/postgres/schema/index.js";
|
||||||
|
import { AgentStore } from "../../core/src/agents/agent-store.js";
|
||||||
|
|
||||||
export { postgresSchema };
|
/*
|
||||||
|
* FNXC:BundledPlugins 2026-08-03-17:18:
|
||||||
|
* The bundled Todo plugin lists project agents through AgentStore. Re-export the source implementation from the runtime shim so clean CLI packaging does not leave a private @fusion/core runtime import unresolved.
|
||||||
|
*/
|
||||||
|
export { AgentStore, postgresSchema };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FNXC:BundledPlugins 2026-07-31-09:55:
|
* FNXC:BundledPlugins 2026-07-31-09:55:
|
||||||
|
|||||||
@@ -57,7 +57,18 @@ vi.mock("../../api", async (importOriginal) => {
|
|||||||
fetchAgents: vi.fn(() => Promise.resolve([])),
|
fetchAgents: vi.fn(() => Promise.resolve([])),
|
||||||
fetchTaskDetail: vi.fn((id: string) => Promise.resolve({ id, title: `Task ${id}` })),
|
fetchTaskDetail: vi.fn((id: string) => Promise.resolve({ id, title: `Task ${id}` })),
|
||||||
fetchUnreadCount: vi.fn(() => Promise.resolve({ unreadCount: 0 })),
|
fetchUnreadCount: vi.fn(() => Promise.resolve({ unreadCount: 0 })),
|
||||||
fetchPluginDashboardViews: vi.fn(() => Promise.resolve([])),
|
// FNXC:TodoNavigation 2026-08-03-17:18: Todo Lists is plugin-owned; keep this fixture production-shaped so the overflow destination uses the runtime manifest contribution.
|
||||||
|
fetchPluginDashboardViews: vi.fn(() => Promise.resolve([{
|
||||||
|
pluginId: "fusion-plugin-todos",
|
||||||
|
view: {
|
||||||
|
viewId: "todos",
|
||||||
|
label: "Todos",
|
||||||
|
componentPath: "./dashboard-view",
|
||||||
|
icon: "CheckSquare",
|
||||||
|
placement: "overflow",
|
||||||
|
order: 70,
|
||||||
|
},
|
||||||
|
}])),
|
||||||
fetchExecutorStats: vi.fn(() => Promise.resolve({
|
fetchExecutorStats: vi.fn(() => Promise.resolve({
|
||||||
globalPause: false,
|
globalPause: false,
|
||||||
enginePaused: false,
|
enginePaused: false,
|
||||||
@@ -554,16 +565,16 @@ describe("Navigation history integration", () => {
|
|||||||
|
|
||||||
const pushCallsBefore = (window.history.pushState as any).mock.calls.length;
|
const pushCallsBefore = (window.history.pushState as any).mock.calls.length;
|
||||||
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||||
fireEvent.click(screen.getByTestId("view-overflow-todos"));
|
fireEvent.click(await screen.findByTestId("view-overflow-plugin-fusion-plugin-todos-todos"));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId("todo-view")).toBeTruthy();
|
expect(screen.getByTestId("todo-view-root")).toBeTruthy();
|
||||||
});
|
});
|
||||||
expect((window.history.pushState as any).mock.calls.length).toBeGreaterThan(pushCallsBefore);
|
expect((window.history.pushState as any).mock.calls.length).toBeGreaterThan(pushCallsBefore);
|
||||||
|
|
||||||
dispatchPopState({ navIndex: 0 });
|
dispatchPopState({ navIndex: 0 });
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByTestId("todo-view")).toBeNull();
|
expect(screen.queryByTestId("todo-view-root")).toBeNull();
|
||||||
expect(screen.getByTestId("board-view")).toBeTruthy();
|
expect(screen.getByTestId("board-view")).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
},
|
},
|
||||||
"types": [
|
"types": [
|
||||||
"node",
|
"node",
|
||||||
"vitest/globals",
|
"vitest/globals"
|
||||||
"@testing-library/jest-dom"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
Reference in New Issue
Block a user