@pixel-point/toolcraft 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate-performance-iteration-execution.test.mjs +2 -2
- package/src/generate-test-demand-only-lifecycle.mjs +14 -3
- package/src/generate-test-dependency-sandbox.mjs +42 -5
- package/src/generate-test-doc-assertions.mjs +68 -18
- package/src/generate-test-doc-assertions.test.mjs +22 -0
- package/src/generate-test-file-assertions.mjs +43 -76
- package/src/generate-test-output-assertions.mjs +95 -0
- package/src/generate-test-package-assertions.mjs +6 -0
- package/src/generate-test-performance-boundary-assertions.mjs +45 -0
- package/src/generate-test-product-fixture.mjs +1 -0
- package/src/generate-test-product-fixture.test.mjs +18 -0
- package/src/generate-test-product-iteration.mjs +1 -0
- package/src/generate-test-product-iteration.test.mjs +1 -0
- package/src/generate-test-standalone-delivery-stages.mjs +14 -6
- package/src/generate.test.mjs +14 -0
- package/templates/runtime/contracts/component-contracts.choices.test.ts +12 -9
- package/templates/runtime/contracts/component-contracts.choices.ts +10 -9
- package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +17 -2
- package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +1 -1
- package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +1 -1
- package/templates/runtime/contracts/component-contracts.runtime.ts +3 -2
- package/templates/runtime/contracts/decision-contracts.test.ts +25 -4
- package/templates/runtime/contracts/decision-contracts.ts +3 -3
- package/templates/runtime/contracts/performance-verification-policy.test.ts +59 -6
- package/templates/runtime/contracts/performance-verification-policy.ts +5 -2
- package/templates/runtime/export/artifact-download.test.ts +71 -0
- package/templates/runtime/export/artifact-download.ts +80 -0
- package/templates/runtime/export/artifact-export-request.ts +23 -0
- package/templates/runtime/export/artifact-export-settings.test.ts +75 -0
- package/templates/runtime/export/artifact-export-settings.ts +96 -0
- package/templates/runtime/export/artifact-frame-renderer.test.ts +107 -0
- package/templates/runtime/export/artifact-frame-renderer.ts +96 -0
- package/templates/runtime/export/artifact-frame-state.test.ts +54 -0
- package/templates/runtime/export/artifact-frame-state.ts +44 -0
- package/templates/runtime/export/artifact-scene-frame.test.ts +84 -0
- package/templates/runtime/export/artifact-scene-frame.ts +118 -0
- package/templates/runtime/export/export-background.ts +65 -0
- package/templates/runtime/export/export-error.ts +43 -0
- package/templates/runtime/export/export.test.ts +6 -186
- package/templates/runtime/export/image-artifact-export.test.ts +111 -0
- package/templates/runtime/export/image-artifact-export.ts +145 -0
- package/templates/runtime/export/index.ts +14 -1
- package/templates/runtime/export/product-export-renderer.test.ts +77 -0
- package/templates/runtime/export/product-export-renderer.ts +63 -0
- package/templates/runtime/export/video-artifact-export.test.ts +166 -0
- package/templates/runtime/export/video-artifact-export.ts +164 -0
- package/templates/runtime/export/video-encoding-backend.ts +124 -0
- package/templates/runtime/export/video-encoding-policy.test.ts +68 -0
- package/templates/runtime/export/video-encoding-policy.ts +86 -0
- package/templates/runtime/export/video-frame-schedule.test.ts +58 -0
- package/templates/runtime/export/video-frame-schedule.ts +42 -0
- package/templates/runtime/index.ts +1 -0
- package/templates/runtime/react/app-shell/runtime-public-api-runtime.test.tsx +291 -0
- package/templates/runtime/react/app-shell/runtime-public-api-test-support.ts +21 -0
- package/templates/runtime/react/app-shell/runtime-public-api.test.tsx +4 -303
- package/templates/runtime/react/app-shell/toolcraft-app.scene-export.test.tsx +12 -27
- package/templates/runtime/react/app-shell/toolcraft-app.tsx +22 -4
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.test.tsx +16 -57
- package/templates/runtime/react/app-shell/toolcraft-source-asset-context.tsx +75 -434
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.test.ts +194 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-owner.ts +227 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-provider-ownership.test.tsx +243 -0
- package/templates/runtime/react/app-shell/toolcraft-source-asset-test-support.ts +51 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.test.ts +138 -0
- package/templates/runtime/react/app-shell/toolcraft-unavailable-resource-proof.ts +378 -0
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +7 -12
- package/templates/runtime/react/controls-panel/__tests__/controls-panel.scene-export-actions.test.tsx +17 -83
- package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +42 -84
- package/templates/runtime/react/controls-panel/actions/export-action-runner.ts +62 -0
- package/templates/runtime/react/panel-host/panel-host-runtime-placement.test.tsx +30 -0
- package/templates/runtime/react/panel-host/panel-host.tsx +4 -0
- package/templates/runtime/scene/scene-bounds.ts +1 -2
- package/templates/runtime/schema/canvas-render-scale.ts +62 -0
- package/templates/runtime/schema/controls-panel-section-id.test.ts +1 -1
- package/templates/runtime/schema/controls-panel-section-roundtrip.test.ts +3 -3
- package/templates/runtime/schema/define-toolcraft.setup-background.test.ts +1 -1
- package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +71 -3
- package/templates/runtime/schema/define-toolcraft.ts +1 -1
- package/templates/runtime/schema/schema-resolvers.ts +0 -56
- package/templates/runtime/schema/types.ts +4 -4
- package/templates/runtime/state/canvas-background-state.ts +6 -0
- package/templates/runtime/testing/performance-render-plan-integration.test.ts +64 -0
- package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +18 -1
- package/templates/starter/AGENTS.md +8 -7
- package/templates/starter/NOTICE.md +6 -0
- package/templates/starter/docs/toolcraft/README.md +2 -1
- package/templates/starter/docs/toolcraft/acceptance-testing.md +8 -6
- package/templates/starter/docs/toolcraft/agent-worklog.md +3 -3
- package/templates/starter/docs/toolcraft/assembly-workflow.md +4 -3
- package/templates/starter/docs/toolcraft/core/media-upload.md +2 -2
- package/templates/starter/docs/toolcraft/core/performance.md +14 -3
- package/templates/starter/docs/toolcraft/core/runtime-boundary.md +3 -1
- package/templates/starter/docs/toolcraft/core/setup-export.md +16 -12
- package/templates/starter/docs/toolcraft/core/timeline-animation.md +4 -4
- package/templates/starter/docs/toolcraft/performance.md +20 -6
- package/templates/starter/docs/toolcraft/renderer-technique.md +1 -1
- package/templates/starter/docs/toolcraft/schema-reference.md +2 -6
- package/templates/starter/docs/toolcraft/workflow.md +7 -2
- package/templates/starter/e2e/app-browser-control-coverage.spec.ts +33 -0
- package/templates/starter/e2e/app-browser-infinity-canvas-evidence.spec.ts +43 -1
- package/templates/starter/e2e/app-browser-render-scale-evidence.spec.ts +60 -6
- package/templates/starter/e2e/app-browser-render-scale-requirements.spec.ts +77 -1
- package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +8 -0
- package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +35 -0
- package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +4 -1
- package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +0 -1
- package/templates/starter/e2e/app-browser-shell.spec.ts +13 -5
- package/templates/starter/e2e/app-performance-path-adapters.ts +8 -1
- package/templates/starter/e2e/app-performance.spec.ts +16 -9
- package/templates/starter/e2e/browser-infinity-canvas-evidence.ts +9 -330
- package/templates/starter/e2e/browser-infinity-canvas-unavailable-image-evidence.ts +370 -0
- package/templates/starter/e2e/browser-media-export-evidence.spec.ts +77 -0
- package/templates/starter/e2e/browser-media-export-evidence.ts +208 -0
- package/templates/starter/e2e/browser-render-scale-evidence.ts +37 -29
- package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +4 -1
- package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +43 -9
- package/templates/starter/e2e/browser-standard-timeline-evidence.ts +106 -0
- package/templates/starter/e2e/decoded-pixel-observation.spec.ts +74 -0
- package/templates/starter/e2e/decoded-pixel-observation.ts +227 -0
- package/templates/starter/e2e/export-artifact-helpers.ts +206 -25
- package/templates/starter/e2e/image-artifact-inspection.spec.ts +41 -0
- package/templates/starter/e2e/image-artifact-inspection.ts +99 -0
- package/templates/starter/e2e/performance-canvas-helpers.ts +22 -21
- package/templates/starter/e2e/performance-canvas-quality-guard.spec.ts +312 -0
- package/templates/starter/e2e/performance-canvas-quality-guard.ts +196 -0
- package/templates/starter/e2e/performance-canvas-quality-observer.ts +351 -0
- package/templates/starter/e2e/performance-control-layout-helpers.ts +2 -2
- package/templates/starter/e2e/performance-path-adapter-contract.ts +4 -4
- package/templates/starter/e2e/performance-path-helpers.spec.ts +203 -70
- package/templates/starter/e2e/performance-path-helpers.ts +210 -91
- package/templates/starter/e2e/performance-pipeline-evidence-continuity.spec.ts +76 -0
- package/templates/starter/e2e/performance-pipeline-evidence-test-fixtures.ts +11 -2
- package/templates/starter/e2e/performance-pipeline-invariants.ts +47 -4
- package/templates/starter/e2e/performance-render-scale-runner.spec.ts +140 -38
- package/templates/starter/e2e/video-artifact-inspection.spec.ts +64 -0
- package/templates/starter/e2e/video-artifact-inspection.ts +281 -0
- package/templates/starter/gitignore +1 -0
- package/templates/starter/package.json +1 -0
- package/templates/starter/scripts/check-toolcraft-docs.mjs +17 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-authority.test.mjs +60 -0
- package/templates/starter/scripts/toolcraft-delivery-plan-errors.test.mjs +7 -2
- package/templates/starter/scripts/toolcraft-delivery-plan.mjs +7 -3
- package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +24 -2
- package/templates/starter/scripts/toolcraft-product-boundary.mjs +12 -0
- package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +46 -0
- package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +85 -20
- package/templates/starter/scripts/toolcraft-product-evidence-import-policy.mjs +43 -0
- package/templates/starter/scripts/toolcraft-product-export-boundary.mjs +168 -0
- package/templates/starter/scripts/toolcraft-typescript-analysis.d.mts +73 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade-test-helpers.mjs +318 -0
- package/templates/starter/scripts/toolcraft-unavailable-resource-evidence-facade.test.mjs +155 -206
- package/templates/starter/scripts/toolcraft-verification-inventory.mjs +1 -0
- package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +51 -0
- package/templates/starter/scripts/toolcraft-vite-env-activation.test.mjs +128 -59
- package/templates/starter/src/app/acceptance/export-artifact-coverage.ts +113 -0
- package/templates/starter/src/app/acceptance/output-background-rules.ts +1 -1
- package/templates/starter/src/app/acceptance/output-export.ts +13 -2
- package/templates/starter/src/app/acceptance/types.ts +7 -0
- package/templates/starter/src/app/acceptance/validate-coverage.ts +2 -1
- package/templates/starter/src/app/starter-acceptance.export-artifact.test.ts +131 -0
- package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +14 -4
- package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +42 -1
- package/templates/starter/src/app/starter-acceptance.ts +2 -0
- package/templates/starter/src/app/starter-performance-test-utils.ts +103 -0
- package/templates/starter/src/app/starter-performance.gates.test.ts +231 -278
- package/templates/starter/src/app/starter-performance.lifecycle.test.ts +261 -0
- package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +2 -0
- package/templates/starter/src/routes/index.tsx +1 -0
- package/templates/runtime/export/export.ts +0 -166
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
|
-
import {
|
|
3
|
-
cleanup,
|
|
4
|
-
fireEvent,
|
|
5
|
-
render,
|
|
6
|
-
screen,
|
|
7
|
-
waitFor,
|
|
8
|
-
} from "@testing-library/react";
|
|
9
|
-
import { afterEach } from "vitest";
|
|
10
|
-
import { describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
2
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
11
3
|
|
|
12
4
|
import * as ToolcraftRuntime from "../../index";
|
|
13
5
|
import type { ToolcraftHistoryPatch } from "../../index";
|
|
14
6
|
import type { ToolcraftRendererPipelineClient } from "../../rendering";
|
|
15
|
-
import { defineToolcraft } from "../../schema/define-toolcraft";
|
|
16
|
-
import type { ToolcraftSourceAssetCoordinator } from "../../source-assets/source-asset-coordinator";
|
|
17
7
|
import type { ToolcraftCommand } from "../../state/types";
|
|
18
8
|
import * as ToolcraftReact from "../index";
|
|
19
9
|
import {
|
|
@@ -32,95 +22,18 @@ import {
|
|
|
32
22
|
ToolcraftRoot,
|
|
33
23
|
type ToolcraftContextValue,
|
|
34
24
|
} from "./toolcraft-root";
|
|
35
|
-
import {
|
|
25
|
+
import { runtimePublicApiSchema } from "./runtime-public-api-test-support";
|
|
36
26
|
import { LayersPanel } from "../layers/layers-panel";
|
|
37
27
|
import { PanelContainer, PanelHost, PanelStage } from "../panel-host/panel-host";
|
|
38
28
|
import { TimelinePanel } from "../timeline/timeline-panel";
|
|
39
29
|
import { ToolbarPanel } from "./toolbar-panel";
|
|
40
30
|
import {
|
|
41
|
-
useToolcraft,
|
|
42
31
|
useToolcraftDispatch,
|
|
43
32
|
useToolcraftSelector,
|
|
44
33
|
} from "./use-toolcraft";
|
|
45
34
|
import { useToolcraftPipeline } from "./use-toolcraft-pipeline";
|
|
46
35
|
import { useToolcraftPipelinePass } from "./use-toolcraft-pipeline-pass";
|
|
47
36
|
|
|
48
|
-
const runtimePublicApiSchema = defineToolcraft({
|
|
49
|
-
canvas: { enabled: true },
|
|
50
|
-
panels: {
|
|
51
|
-
controls: {
|
|
52
|
-
sections: [
|
|
53
|
-
{
|
|
54
|
-
controls: {
|
|
55
|
-
opacity: {
|
|
56
|
-
defaultValue: 75,
|
|
57
|
-
target: "selectedLayer.opacity",
|
|
58
|
-
type: "slider",
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
title: "Controls",
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const runtimePublicApiPanelActionSchema = defineToolcraft({
|
|
69
|
-
canvas: { enabled: true },
|
|
70
|
-
panels: {
|
|
71
|
-
controls: {
|
|
72
|
-
sections: [
|
|
73
|
-
{
|
|
74
|
-
actionGroup: "secondary",
|
|
75
|
-
controls: {
|
|
76
|
-
action: {
|
|
77
|
-
actions: [
|
|
78
|
-
{
|
|
79
|
-
label: "Resolve media",
|
|
80
|
-
value: "resolve-media",
|
|
81
|
-
variant: "default",
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
target: "panel.actions",
|
|
85
|
-
type: "panelActions",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
title: "Controls",
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
function createRuntimePublicApiSourceAssetCoordinator(
|
|
96
|
-
resolveResource?: ToolcraftSourceAssetCoordinator["resolveResource"],
|
|
97
|
-
): ToolcraftSourceAssetCoordinator {
|
|
98
|
-
const idleOperation = Object.freeze({
|
|
99
|
-
phase: "idle" as const,
|
|
100
|
-
target: "source",
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
cancelTarget: vi.fn(),
|
|
105
|
-
clearPresentationFeedback: vi.fn(),
|
|
106
|
-
dispose: vi.fn(async () => undefined),
|
|
107
|
-
getOperation: () => idleOperation,
|
|
108
|
-
getPresentation: () => null,
|
|
109
|
-
hydrateBinaryMedia: vi.fn(async () => undefined),
|
|
110
|
-
hydrateModels: vi.fn(async () => undefined),
|
|
111
|
-
importBatch: vi.fn(async () => ({
|
|
112
|
-
assetIds: [] as const,
|
|
113
|
-
kind: "committed" as const,
|
|
114
|
-
})),
|
|
115
|
-
repairModel: vi.fn(async () => ({ kind: "cancelled" as const })),
|
|
116
|
-
reportPresentationFeedback: vi.fn(),
|
|
117
|
-
...(resolveResource ? { resolveResource } : {}),
|
|
118
|
-
subscribe: () => () => undefined,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
afterEach(cleanup);
|
|
123
|
-
|
|
124
37
|
describe("Toolcraft template runtime public API", () => {
|
|
125
38
|
it("exports selector hooks without exposing the internal store context", () => {
|
|
126
39
|
expect(ToolcraftRuntime).not.toHaveProperty(
|
|
@@ -157,6 +70,7 @@ describe("Toolcraft template runtime public API", () => {
|
|
|
157
70
|
expectTypeOf<keyof ToolcraftAppComposition>().toEqualTypeOf<
|
|
158
71
|
| "canvasContent"
|
|
159
72
|
| "controlRenderers"
|
|
73
|
+
| "exportRenderer"
|
|
160
74
|
| "modelPresentation"
|
|
161
75
|
| "onPanelAction"
|
|
162
76
|
| "renderDefaultCanvasMedia"
|
|
@@ -170,6 +84,7 @@ describe("Toolcraft template runtime public API", () => {
|
|
|
170
84
|
| "canvasContent"
|
|
171
85
|
| "className"
|
|
172
86
|
| "controlRenderers"
|
|
87
|
+
| "exportRenderer"
|
|
173
88
|
| "modelPresentation"
|
|
174
89
|
| "onPanelAction"
|
|
175
90
|
| "renderDefaultCanvasMedia"
|
|
@@ -193,11 +108,8 @@ describe("Toolcraft template runtime public API", () => {
|
|
|
193
108
|
expectTypeOf<keyof ToolcraftPanelActionContext>().toEqualTypeOf<
|
|
194
109
|
| "action"
|
|
195
110
|
| "dispatch"
|
|
196
|
-
| "renderRuntimeSceneToCanvas"
|
|
197
111
|
| "reportFeedback"
|
|
198
|
-
| "renderModelsToCanvas"
|
|
199
112
|
| "resolveMediaResource"
|
|
200
|
-
| "resolveSceneExportFrame"
|
|
201
113
|
| "rendererPipeline"
|
|
202
114
|
| "reportProgress"
|
|
203
115
|
| "state"
|
|
@@ -238,63 +150,6 @@ describe("Toolcraft template runtime public API", () => {
|
|
|
238
150
|
>().toEqualTypeOf<RuntimePanelKeys | "groupCreation">();
|
|
239
151
|
});
|
|
240
152
|
|
|
241
|
-
it("binds the source asset resolver into panel action context", async () => {
|
|
242
|
-
const bytes = new Uint8Array([11, 22, 33]);
|
|
243
|
-
const resolveResource = vi.fn<
|
|
244
|
-
NonNullable<ToolcraftSourceAssetCoordinator["resolveResource"]>
|
|
245
|
-
>(async () => bytes);
|
|
246
|
-
const coordinator = createRuntimePublicApiSourceAssetCoordinator(
|
|
247
|
-
resolveResource,
|
|
248
|
-
);
|
|
249
|
-
const signal = new AbortController().signal;
|
|
250
|
-
let resolvedBytes: Uint8Array | null | undefined;
|
|
251
|
-
|
|
252
|
-
render(
|
|
253
|
-
<ToolcraftRoot schema={runtimePublicApiPanelActionSchema}>
|
|
254
|
-
<ToolcraftSourceAssetCoordinatorContext.Provider value={coordinator}>
|
|
255
|
-
<ControlsPanel
|
|
256
|
-
framed={false}
|
|
257
|
-
onPanelAction={async ({ resolveMediaResource }) => {
|
|
258
|
-
resolvedBytes = await resolveMediaResource("media:known", {
|
|
259
|
-
signal,
|
|
260
|
-
});
|
|
261
|
-
}}
|
|
262
|
-
/>
|
|
263
|
-
</ToolcraftSourceAssetCoordinatorContext.Provider>
|
|
264
|
-
</ToolcraftRoot>,
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
fireEvent.click(screen.getByRole("button", { name: "Resolve media" }));
|
|
268
|
-
|
|
269
|
-
await waitFor(() => expect(resolvedBytes).toEqual(bytes));
|
|
270
|
-
expect(resolveResource).toHaveBeenCalledWith("media:known", { signal });
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("returns null from panel action media resolution when no resolver exists", async () => {
|
|
274
|
-
const coordinator = createRuntimePublicApiSourceAssetCoordinator();
|
|
275
|
-
const signal = new AbortController().signal;
|
|
276
|
-
let resolvedBytes: Uint8Array | null | undefined;
|
|
277
|
-
|
|
278
|
-
render(
|
|
279
|
-
<ToolcraftRoot schema={runtimePublicApiPanelActionSchema}>
|
|
280
|
-
<ToolcraftSourceAssetCoordinatorContext.Provider value={coordinator}>
|
|
281
|
-
<ControlsPanel
|
|
282
|
-
framed={false}
|
|
283
|
-
onPanelAction={async ({ resolveMediaResource }) => {
|
|
284
|
-
resolvedBytes = await resolveMediaResource("media:missing", {
|
|
285
|
-
signal,
|
|
286
|
-
});
|
|
287
|
-
}}
|
|
288
|
-
/>
|
|
289
|
-
</ToolcraftSourceAssetCoordinatorContext.Provider>
|
|
290
|
-
</ToolcraftRoot>,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
fireEvent.click(screen.getByRole("button", { name: "Resolve media" }));
|
|
294
|
-
|
|
295
|
-
await waitFor(() => expect(resolvedBytes).toBeNull());
|
|
296
|
-
});
|
|
297
|
-
|
|
298
153
|
it("keeps shell and panel host props separate", () => {
|
|
299
154
|
expectTypeOf<
|
|
300
155
|
keyof React.ComponentProps<typeof CanvasShell>
|
|
@@ -380,158 +235,4 @@ describe("Toolcraft template runtime public API", () => {
|
|
|
380
235
|
expect(toolbarWithThemeToggle).toBeTruthy();
|
|
381
236
|
});
|
|
382
237
|
|
|
383
|
-
it("keeps broad consumers live while isolating selector and dispatch consumers", () => {
|
|
384
|
-
const renders = {
|
|
385
|
-
broad: 0,
|
|
386
|
-
dispatch: 0,
|
|
387
|
-
playback: 0,
|
|
388
|
-
value: 0,
|
|
389
|
-
viewport: 0,
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
function BroadProbe() {
|
|
393
|
-
const { state } = useToolcraft();
|
|
394
|
-
|
|
395
|
-
renders.broad += 1;
|
|
396
|
-
|
|
397
|
-
return <output data-testid="broad-time">{state.timeline.currentTimeSeconds}</output>;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function DispatchProbe() {
|
|
401
|
-
const dispatch = useToolcraftDispatch();
|
|
402
|
-
|
|
403
|
-
renders.dispatch += 1;
|
|
404
|
-
|
|
405
|
-
return (
|
|
406
|
-
<button
|
|
407
|
-
type="button"
|
|
408
|
-
onClick={() =>
|
|
409
|
-
dispatch({
|
|
410
|
-
target: "selectedLayer.opacity",
|
|
411
|
-
type: "controls.setValue",
|
|
412
|
-
value: 50,
|
|
413
|
-
})
|
|
414
|
-
}
|
|
415
|
-
>
|
|
416
|
-
Set value
|
|
417
|
-
</button>
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function ValueProbe() {
|
|
422
|
-
const value = useToolcraftSelector(
|
|
423
|
-
(state) => state.values["selectedLayer.opacity"],
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
renders.value += 1;
|
|
427
|
-
|
|
428
|
-
return <output data-testid="selected-value">{String(value)}</output>;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function PlaybackProbe() {
|
|
432
|
-
const currentTimeSeconds = useToolcraftSelector(
|
|
433
|
-
(state) => state.timeline.currentTimeSeconds,
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
renders.playback += 1;
|
|
437
|
-
|
|
438
|
-
return <output data-testid="playback-time">{currentTimeSeconds}</output>;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function ViewportProbe() {
|
|
442
|
-
const offset = useToolcraftSelector(
|
|
443
|
-
(state) => ({ ...state.canvas.offset }),
|
|
444
|
-
(previous, next) => previous.x === next.x && previous.y === next.y,
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
renders.viewport += 1;
|
|
448
|
-
|
|
449
|
-
return <output data-testid="viewport-offset">{offset.x},{offset.y}</output>;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function TransientProbe() {
|
|
453
|
-
const store = useToolcraftStore();
|
|
454
|
-
|
|
455
|
-
return (
|
|
456
|
-
<div>
|
|
457
|
-
<button
|
|
458
|
-
type="button"
|
|
459
|
-
onClick={() =>
|
|
460
|
-
store.dispatchTransient({
|
|
461
|
-
currentTimeSeconds: 2,
|
|
462
|
-
type: "timeline.setCurrentTime",
|
|
463
|
-
})
|
|
464
|
-
}
|
|
465
|
-
>
|
|
466
|
-
Set playback time
|
|
467
|
-
</button>
|
|
468
|
-
<button
|
|
469
|
-
type="button"
|
|
470
|
-
onClick={() =>
|
|
471
|
-
store.dispatchTransient({
|
|
472
|
-
offset: { x: 0, y: 0 },
|
|
473
|
-
type: "canvas.setViewport",
|
|
474
|
-
zoom: 150,
|
|
475
|
-
})
|
|
476
|
-
}
|
|
477
|
-
>
|
|
478
|
-
Set viewport zoom
|
|
479
|
-
</button>
|
|
480
|
-
</div>
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
render(
|
|
485
|
-
<ToolcraftRoot schema={runtimePublicApiSchema}>
|
|
486
|
-
<BroadProbe />
|
|
487
|
-
<DispatchProbe />
|
|
488
|
-
<ValueProbe />
|
|
489
|
-
<PlaybackProbe />
|
|
490
|
-
<ViewportProbe />
|
|
491
|
-
<TransientProbe />
|
|
492
|
-
</ToolcraftRoot>,
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
expect(renders).toEqual({
|
|
496
|
-
broad: 1,
|
|
497
|
-
dispatch: 1,
|
|
498
|
-
playback: 1,
|
|
499
|
-
value: 1,
|
|
500
|
-
viewport: 1,
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
fireEvent.click(screen.getByRole("button", { name: "Set value" }));
|
|
504
|
-
|
|
505
|
-
expect(screen.getByTestId("selected-value").textContent).toBe("50");
|
|
506
|
-
expect(renders).toEqual({
|
|
507
|
-
broad: 2,
|
|
508
|
-
dispatch: 1,
|
|
509
|
-
playback: 1,
|
|
510
|
-
value: 2,
|
|
511
|
-
viewport: 1,
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
fireEvent.click(screen.getByRole("button", { name: "Set playback time" }));
|
|
515
|
-
|
|
516
|
-
expect(screen.getByTestId("broad-time").textContent).toBe("2");
|
|
517
|
-
expect(screen.getByTestId("playback-time").textContent).toBe("2");
|
|
518
|
-
expect(renders).toEqual({
|
|
519
|
-
broad: 3,
|
|
520
|
-
dispatch: 1,
|
|
521
|
-
playback: 2,
|
|
522
|
-
value: 2,
|
|
523
|
-
viewport: 1,
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
fireEvent.click(screen.getByRole("button", { name: "Set viewport zoom" }));
|
|
527
|
-
|
|
528
|
-
expect(screen.getByTestId("viewport-offset").textContent).toBe("0,0");
|
|
529
|
-
expect(renders).toEqual({
|
|
530
|
-
broad: 4,
|
|
531
|
-
dispatch: 1,
|
|
532
|
-
playback: 2,
|
|
533
|
-
value: 2,
|
|
534
|
-
viewport: 1,
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
238
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cleanup,
|
|
2
|
-
import { afterEach, describe, expect, it
|
|
1
|
+
import { cleanup, render } from "@testing-library/react";
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
import { defineToolcraft } from "../../schema/define-toolcraft";
|
|
5
5
|
import { ToolcraftApp } from "./toolcraft-app";
|
|
@@ -10,7 +10,7 @@ afterEach(() => {
|
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
describe("ToolcraftApp scene export boundary", () => {
|
|
13
|
-
it("
|
|
13
|
+
it("fails before mount when a typed product export has no shared renderer", () => {
|
|
14
14
|
const schema = defineToolcraft({
|
|
15
15
|
canvas: {
|
|
16
16
|
enabled: true,
|
|
@@ -26,6 +26,7 @@ describe("ToolcraftApp scene export boundary", () => {
|
|
|
26
26
|
actions: [
|
|
27
27
|
{
|
|
28
28
|
label: "Export image",
|
|
29
|
+
role: "export-image",
|
|
29
30
|
value: "export",
|
|
30
31
|
variant: "default",
|
|
31
32
|
},
|
|
@@ -40,29 +41,13 @@ describe("ToolcraftApp scene export boundary", () => {
|
|
|
40
41
|
},
|
|
41
42
|
},
|
|
42
43
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
onPanelAction={({ resolveSceneExportFrame }) => {
|
|
52
|
-
frame = resolveSceneExportFrame();
|
|
53
|
-
}}
|
|
54
|
-
sceneBoundsProvider={sceneBoundsProvider}
|
|
55
|
-
schema={schema}
|
|
56
|
-
/>,
|
|
57
|
-
);
|
|
58
|
-
expect(screen.getByText("Infinity canvas")).toBeTruthy();
|
|
59
|
-
fireEvent.click(screen.getByRole("switch"));
|
|
60
|
-
fireEvent.click(screen.getByRole("button", { name: "Export image" }));
|
|
61
|
-
|
|
62
|
-
expect(sceneBoundsProvider).toHaveBeenCalledTimes(1);
|
|
63
|
-
expect(frame).toEqual({
|
|
64
|
-
frame: { height: 40, width: 80, x: -20, y: 10 },
|
|
65
|
-
ok: true,
|
|
66
|
-
});
|
|
44
|
+
expect(() =>
|
|
45
|
+
render(
|
|
46
|
+
<ToolcraftApp
|
|
47
|
+
canvasContent={<div data-testid="product-scene" />}
|
|
48
|
+
schema={schema}
|
|
49
|
+
/>,
|
|
50
|
+
),
|
|
51
|
+
).toThrow("Product-owned export actions require exportRenderer");
|
|
67
52
|
});
|
|
68
53
|
});
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
type ToolcraftProductSceneBoundsProvider,
|
|
10
10
|
} from "../../scene";
|
|
11
11
|
import type { ToolcraftState } from "../../state/types";
|
|
12
|
+
import {
|
|
13
|
+
getToolcraftExportRendererCoverageErrors,
|
|
14
|
+
type ToolcraftProductExportRenderer,
|
|
15
|
+
} from "../../export";
|
|
12
16
|
import { CanvasShell } from "../canvas/canvas-shell";
|
|
13
17
|
import {
|
|
14
18
|
ControlsPanel,
|
|
@@ -28,6 +32,7 @@ import { useToolcraftPersistenceStatus } from "./use-toolcraft-persistence";
|
|
|
28
32
|
export type ToolcraftAppComposition = {
|
|
29
33
|
canvasContent?: React.ReactNode;
|
|
30
34
|
controlRenderers?: ToolcraftControlRendererMap;
|
|
35
|
+
exportRenderer?: ToolcraftProductExportRenderer;
|
|
31
36
|
modelPresentation?: ToolcraftModelPresentationMode;
|
|
32
37
|
onPanelAction?: ToolcraftPanelActionHandler;
|
|
33
38
|
renderDefaultCanvasMedia?: boolean;
|
|
@@ -122,6 +127,7 @@ function ToolcraftAppContent({
|
|
|
122
127
|
|
|
123
128
|
export function ToolcraftApp({
|
|
124
129
|
canvasContent,
|
|
130
|
+
exportRenderer,
|
|
125
131
|
modelPresentation,
|
|
126
132
|
renderDefaultCanvasMedia = true,
|
|
127
133
|
rendererPipelineRegistration,
|
|
@@ -132,12 +138,24 @@ export function ToolcraftApp({
|
|
|
132
138
|
const suppressedModelTargets = modelPresentation?.mode === "custom"
|
|
133
139
|
? modelPresentation.consumers.map(({ sourceTarget }) => sourceTarget)
|
|
134
140
|
: [];
|
|
141
|
+
const productSceneRequired =
|
|
142
|
+
React.Children.count(canvasContent) > 0 ||
|
|
143
|
+
rendererPipelineRegistration !== undefined ||
|
|
144
|
+
suppressedModelTargets.length > 0;
|
|
145
|
+
const exportRendererErrors = getToolcraftExportRendererCoverageErrors({
|
|
146
|
+
exportRenderer,
|
|
147
|
+
productSceneRequired,
|
|
148
|
+
schema,
|
|
149
|
+
});
|
|
150
|
+
if (exportRendererErrors.length > 0) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Toolcraft export renderer configuration is invalid:\n- ${exportRendererErrors.join("\n- ")}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
135
155
|
const sceneExport: ToolcraftControlsSceneExport = Object.freeze({
|
|
136
156
|
...(sceneBoundsProvider ? { boundsProvider: sceneBoundsProvider } : {}),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
rendererPipelineRegistration !== undefined ||
|
|
140
|
-
suppressedModelTargets.length > 0,
|
|
157
|
+
...(exportRenderer ? { exportRenderer } : {}),
|
|
158
|
+
productSceneRequired,
|
|
141
159
|
visibility: createToolcraftRuntimeSceneVisibility({
|
|
142
160
|
renderDefaultImages: renderDefaultCanvasMedia,
|
|
143
161
|
suppressedModelTargets,
|
|
@@ -10,66 +10,23 @@ import type { ToolcraftSourceAssetCoordinator } from "../../source-assets/source
|
|
|
10
10
|
import { useOptionalToolcraftModelRenderHost } from "../model-rendering/model-render-provider";
|
|
11
11
|
import { ToolcraftModelPresentationModeContext } from "../model-rendering/model-presentation-mode";
|
|
12
12
|
import { ToolcraftRoot } from "./toolcraft-root";
|
|
13
|
+
import { ToolcraftSourceAssetProvider } from "./toolcraft-source-asset-context";
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
ToolcraftSourceAssetCoordinatorProbe as CoordinatorProbe,
|
|
16
|
+
createTestSourceAssetCoordinator as createTestCoordinator,
|
|
17
|
+
createTestSourceAssetStore as createTestStore,
|
|
18
|
+
} from "./toolcraft-source-asset-test-support";
|
|
17
19
|
|
|
18
20
|
afterEach(() => {
|
|
19
21
|
cleanup();
|
|
20
22
|
vi.unstubAllGlobals();
|
|
21
23
|
});
|
|
22
24
|
|
|
23
|
-
function CoordinatorProbe({
|
|
24
|
-
capture,
|
|
25
|
-
}: {
|
|
26
|
-
capture: (coordinator: ToolcraftSourceAssetCoordinator) => void;
|
|
27
|
-
}) {
|
|
28
|
-
capture(useToolcraftSourceAssetCoordinator());
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
function ModelRenderHostProbe(): React.JSX.Element {
|
|
33
26
|
const host = useOptionalToolcraftModelRenderHost();
|
|
34
27
|
return <span data-testid="model-render-host">{host ? "ready" : "missing"}</span>;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
function createTestCoordinator(
|
|
38
|
-
dispose: ToolcraftSourceAssetCoordinator["dispose"] = vi.fn(
|
|
39
|
-
async () => undefined,
|
|
40
|
-
),
|
|
41
|
-
): ToolcraftSourceAssetCoordinator {
|
|
42
|
-
const idleOperation = Object.freeze({
|
|
43
|
-
phase: "idle" as const,
|
|
44
|
-
target: "source.test",
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
cancelTarget: vi.fn(),
|
|
49
|
-
clearPresentationFeedback: vi.fn(),
|
|
50
|
-
dispose,
|
|
51
|
-
getOperation: () => idleOperation,
|
|
52
|
-
getPresentation: () => null,
|
|
53
|
-
hydrateBinaryMedia: vi.fn(async () => undefined),
|
|
54
|
-
hydrateModels: vi.fn(async () => undefined),
|
|
55
|
-
importBatch: vi.fn(async () => ({
|
|
56
|
-
assetIds: [] as const,
|
|
57
|
-
kind: "committed" as const,
|
|
58
|
-
})),
|
|
59
|
-
repairModel: vi.fn(async () => ({ kind: "cancelled" as const })),
|
|
60
|
-
reportPresentationFeedback: vi.fn(),
|
|
61
|
-
subscribe: () => () => undefined,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function createTestStore() {
|
|
66
|
-
return createToolcraftExternalStore(
|
|
67
|
-
createToolcraftState(
|
|
68
|
-
defineToolcraft({ canvas: { enabled: true, upload: true }, panels: {} }),
|
|
69
|
-
),
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
30
|
describe("Toolcraft source asset provider", () => {
|
|
74
31
|
it("shares one app-owned coordinator and disposes it on unmount", async () => {
|
|
75
32
|
const schema = defineToolcraft({
|
|
@@ -108,6 +65,7 @@ describe("Toolcraft source asset provider", () => {
|
|
|
108
65
|
store={store}
|
|
109
66
|
>
|
|
110
67
|
<CoordinatorProbe capture={(value) => coordinators.push(value)} />
|
|
68
|
+
<span data-testid="strict-source-asset-child" />
|
|
111
69
|
</ToolcraftSourceAssetProvider>
|
|
112
70
|
</React.StrictMode>,
|
|
113
71
|
);
|
|
@@ -116,6 +74,7 @@ describe("Toolcraft source asset provider", () => {
|
|
|
116
74
|
expect(createCoordinator).toHaveBeenCalledWith(store);
|
|
117
75
|
expect(coordinators.length).toBeGreaterThanOrEqual(2);
|
|
118
76
|
expect(coordinators.every((value) => value === coordinator)).toBe(true);
|
|
77
|
+
expect(rendered.getByTestId("strict-source-asset-child")).toBeDefined();
|
|
119
78
|
|
|
120
79
|
rendered.unmount();
|
|
121
80
|
await waitFor(() => expect(coordinator.dispose).toHaveBeenCalledTimes(1));
|
|
@@ -420,16 +379,16 @@ describe("Toolcraft source asset provider", () => {
|
|
|
420
379
|
[new Error("rollback failed")],
|
|
421
380
|
"cleanup failed",
|
|
422
381
|
);
|
|
423
|
-
const coordinator = createTestCoordinator(
|
|
424
|
-
vi.fn(async () => Promise.reject(failure)),
|
|
425
|
-
);
|
|
426
|
-
const
|
|
382
|
+
const coordinator = createTestCoordinator({
|
|
383
|
+
dispose: vi.fn(async () => Promise.reject(failure)),
|
|
384
|
+
});
|
|
385
|
+
const onError = vi.fn();
|
|
427
386
|
const onUnhandledRejection = vi.fn();
|
|
428
387
|
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
429
388
|
const rendered = render(
|
|
430
389
|
<ToolcraftSourceAssetProvider
|
|
431
390
|
createCoordinator={() => coordinator}
|
|
432
|
-
|
|
391
|
+
onError={onError}
|
|
433
392
|
store={store}
|
|
434
393
|
>
|
|
435
394
|
<CoordinatorProbe capture={() => undefined} />
|
|
@@ -437,7 +396,7 @@ describe("Toolcraft source asset provider", () => {
|
|
|
437
396
|
);
|
|
438
397
|
|
|
439
398
|
rendered.unmount();
|
|
440
|
-
await waitFor(() => expect(
|
|
399
|
+
await waitFor(() => expect(onError).toHaveBeenCalledWith(failure));
|
|
441
400
|
await Promise.resolve();
|
|
442
401
|
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
443
402
|
|
|
@@ -448,9 +407,9 @@ describe("Toolcraft source asset provider", () => {
|
|
|
448
407
|
const failure = new Error("repository owner disposal failed");
|
|
449
408
|
const reportError = vi.fn();
|
|
450
409
|
vi.stubGlobal("reportError", reportError);
|
|
451
|
-
const coordinator = createTestCoordinator(
|
|
452
|
-
vi.fn(async () => Promise.reject(failure)),
|
|
453
|
-
);
|
|
410
|
+
const coordinator = createTestCoordinator({
|
|
411
|
+
dispose: vi.fn(async () => Promise.reject(failure)),
|
|
412
|
+
});
|
|
454
413
|
const rendered = render(
|
|
455
414
|
<ToolcraftSourceAssetProvider
|
|
456
415
|
createCoordinator={() => coordinator}
|