@snowcone-app/canvas 0.18.1 → 0.19.1

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.
Files changed (32) hide show
  1. package/dist/{EditorCanvasImpl-Bv4yagew.js → EditorCanvasImpl-_3xCnLkx.js} +4 -4
  2. package/dist/{EditorCanvasImpl-Bv4yagew.js.map → EditorCanvasImpl-_3xCnLkx.js.map} +1 -1
  3. package/dist/EditorContext-5QpqAhjh.js.map +1 -1
  4. package/dist/{SnowconeCanvas-C_O-aeaQ.js → SnowconeCanvas-3PA17na3.js} +2 -2
  5. package/dist/{SnowconeCanvas-C_O-aeaQ.js.map → SnowconeCanvas-3PA17na3.js.map} +1 -1
  6. package/dist/api/embed.d.ts +2 -2
  7. package/dist/api/testing.d.ts +3 -0
  8. package/dist/components/editor/EditorBuy.d.ts +16 -5
  9. package/dist/components/editor/RootInternals.d.ts +5 -0
  10. package/dist/components/editor/commerceStore.d.ts +68 -0
  11. package/dist/components/editor/index.d.ts +4 -0
  12. package/dist/components/editor/useProductSelection.d.ts +39 -0
  13. package/dist/design-builder-v4935TeS.js +343 -0
  14. package/dist/design-builder-v4935TeS.js.map +1 -0
  15. package/dist/embed-D2QOpJXG.js +1090 -0
  16. package/dist/embed-D2QOpJXG.js.map +1 -0
  17. package/dist/embed.mjs +27 -25
  18. package/dist/embed.mjs.map +1 -1
  19. package/dist/errors/editorError.d.ts +15 -1
  20. package/dist/index.mjs +40 -39
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/template-fields-Dr3fMItw.js +329 -0
  23. package/dist/template-fields-Dr3fMItw.js.map +1 -0
  24. package/dist/testing/assertRenders.d.ts +53 -0
  25. package/dist/testing/failureFixtures.d.ts +25 -0
  26. package/dist/testing.mjs +191 -80
  27. package/dist/testing.mjs.map +1 -1
  28. package/package.json +3 -3
  29. package/dist/embed-Cdbjay9i.js +0 -961
  30. package/dist/embed-Cdbjay9i.js.map +0 -1
  31. package/dist/template-fields-DgoBtajp.js +0 -668
  32. package/dist/template-fields-DgoBtajp.js.map +0 -1
@@ -0,0 +1,25 @@
1
+ import { RealtimeGrant } from '@snowcone-app/sdk';
2
+ /**
3
+ * An origin that is deliberately on NO shop's asset-origin allowlist. The
4
+ * `.invalid` TLD is reserved (RFC 2606) — it can never resolve, and the
5
+ * allowlist check denies it before any fetch is attempted, so the failure is
6
+ * deterministic and free.
7
+ */
8
+ export declare const TEST_403_ASSET_URL = "https://assets.test-403.invalid/denied.png";
9
+ /**
10
+ * A design whose background asset the shop's allowlist is guaranteed to deny —
11
+ * the deterministic allowlist-denial (403) trigger.
12
+ */
13
+ export declare function test403Design(): import('../design/design-builder.js').DesignBuilder<"headline", "art">;
14
+ /**
15
+ * A design that gates `<Editor.Buy>` out of the box: its one personalization
16
+ * layer (`'name'`, seed `'YOUR NAME'`) is required and still equals its seed,
17
+ * so Buy renders `data-sc-buy-state="incomplete"` with
18
+ * `data-sc-buy-missing="layer-seed:name"` until a real value is typed.
19
+ */
20
+ export declare function testIncompleteDesign(): import('../design/design-builder.js').DesignBuilder<"name", never>;
21
+ /**
22
+ * A grant factory that always rejects — the deterministic "grant fetch
23
+ * failed" trigger. The rejection reason is stable for assertions.
24
+ */
25
+ export declare function failingGrant(reason?: string): () => Promise<RealtimeGrant>;
package/dist/testing.mjs CHANGED
@@ -1,53 +1,55 @@
1
- import { e as U, g as I, a as R } from "./editorErrors-DXNfVXtD.js";
2
- import { jsx as a } from "react/jsx-runtime";
3
- import { useState as d } from "react";
4
- import { E as s } from "./EditorContext-5QpqAhjh.js";
5
- import { T as m } from "./ThemeContext-Rjz25x9X.js";
6
- import { E as u, A as h, g, I as E } from "./FontAnalyzer-DIlqmyuf.js";
7
- const M = ({
1
+ import { e as ie, g as oe, a as ae } from "./editorErrors-DXNfVXtD.js";
2
+ import { fetchMockupImage as S, isSnowconeSdkError as b, RenderSession as _, mintRealtimeGrant as R } from "@snowcone-app/sdk";
3
+ import { s as I, d as A } from "./design-builder-v4935TeS.js";
4
+ import { E as y, g as m } from "./EditorContext-5QpqAhjh.js";
5
+ import { jsx as u } from "react/jsx-runtime";
6
+ import { useState as D } from "react";
7
+ import { T as p } from "./ThemeContext-Rjz25x9X.js";
8
+ import { E as T, A as k, g as F, I as x } from "./FontAnalyzer-DIlqmyuf.js";
9
+ const G = ({
8
10
  children: t,
9
- initialElements: e = [],
10
- initialArtboards: n,
11
+ initialElements: n = [],
12
+ initialArtboards: e,
11
13
  initialSelection: r = null,
12
- initialMultiSelection: o = [],
13
- initialArtboardConfig: l = {}
14
+ initialMultiSelection: i = [],
15
+ initialArtboardConfig: o = {}
14
16
  }) => {
15
- const [f] = d(() => {
16
- const c = new u();
17
- return n && n.length > 0 ? n.forEach((i) => {
18
- c.createArtboard({
19
- name: i.name,
20
- width: i.width,
21
- height: i.height,
22
- backgroundColor: i.backgroundColor
17
+ const [l] = D(() => {
18
+ const a = new T();
19
+ return e && e.length > 0 ? e.forEach((c) => {
20
+ a.createArtboard({
21
+ name: c.name,
22
+ width: c.width,
23
+ height: c.height,
24
+ backgroundColor: c.backgroundColor
23
25
  });
24
- }) : c.createArtboard({
26
+ }) : a.createArtboard({
25
27
  name: "Test Artboard",
26
28
  width: 1200,
27
29
  height: 1200,
28
30
  backgroundColor: "#FFFFFF",
29
- ...l
30
- }), c;
31
+ ...o
32
+ }), a;
31
33
  });
32
- return /* @__PURE__ */ a(m, { children: /* @__PURE__ */ a(
33
- s,
34
+ return /* @__PURE__ */ u(p, { children: /* @__PURE__ */ u(
35
+ y,
34
36
  {
35
- initialArtboardConfig: l,
37
+ initialArtboardConfig: o,
36
38
  children: t
37
39
  }
38
40
  ) });
39
- }, S = ({
41
+ }, J = ({
40
42
  children: t,
41
- initialElements: e = [],
42
- initialSelection: n = null,
43
+ initialElements: n = [],
44
+ initialSelection: e = null,
43
45
  initialMultiSelection: r = [],
44
- initialArtboardConfig: o = {}
45
- }) => /* @__PURE__ */ a(m, { children: /* @__PURE__ */ a(s, { initialArtboardConfig: o, children: t }) });
46
- function p(t = "custom", e = {}) {
47
- const n = g(t);
48
- if (!n || !n.Component)
46
+ initialArtboardConfig: i = {}
47
+ }) => /* @__PURE__ */ u(p, { children: /* @__PURE__ */ u(y, { initialArtboardConfig: i, children: t }) });
48
+ function W(t = "custom", n = {}) {
49
+ const e = F(t);
50
+ if (!e || !e.Component)
49
51
  throw new Error(`Transform type "${t}" not found in registry`);
50
- return new n.Component({
52
+ return new e.Component({
51
53
  transformType: t,
52
54
  text: "Test Text",
53
55
  fontSize: 32,
@@ -56,12 +58,12 @@ function p(t = "custom", e = {}) {
56
58
  x: 100,
57
59
  y: 100,
58
60
  rotation: 0,
59
- ...e
61
+ ...n
60
62
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- test utility: overrides merge Partial<TextElement> properties with config, which are structurally compatible but not type-assignable
61
63
  });
62
64
  }
63
- function k(t = {}) {
64
- return new E({
65
+ function j(t = {}) {
66
+ return new x({
65
67
  imageUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
66
68
  imageAspectRatio: 1,
67
69
  x: 100,
@@ -70,8 +72,8 @@ function k(t = {}) {
70
72
  ...t
71
73
  });
72
74
  }
73
- function v(t = {}) {
74
- return new h({
75
+ function z(t = {}) {
76
+ return new k({
75
77
  name: "Test Artboard",
76
78
  width: 1200,
77
79
  height: 1200,
@@ -81,65 +83,174 @@ function v(t = {}) {
81
83
  ...t
82
84
  });
83
85
  }
84
- async function A(t, e = 5e3, n = 100) {
86
+ async function U(t, n = 5e3, e = 100) {
85
87
  const r = Date.now();
86
88
  for (; !t(); ) {
87
- if (Date.now() - r > e)
88
- throw new Error(`Timeout waiting for condition after ${e}ms`);
89
- await new Promise((o) => setTimeout(o, n));
89
+ if (Date.now() - r > n)
90
+ throw new Error(`Timeout waiting for condition after ${n}ms`);
91
+ await new Promise((i) => setTimeout(i, e));
90
92
  }
91
93
  }
92
- async function C(t, e = 5e3) {
93
- let n = null;
94
- return await A(() => !1, e), n;
94
+ async function Q(t, n = 5e3) {
95
+ let e = null;
96
+ return await U(() => !1, n), e;
95
97
  }
96
- function T(t, e = {}) {
97
- const n = new KeyboardEvent("keydown", {
98
+ function V(t, n = {}) {
99
+ const e = new KeyboardEvent("keydown", {
98
100
  key: t,
99
- ctrlKey: e.ctrl,
100
- shiftKey: e.shift,
101
- altKey: e.alt,
102
- metaKey: e.meta,
101
+ ctrlKey: n.ctrl,
102
+ shiftKey: n.shift,
103
+ altKey: n.alt,
104
+ metaKey: n.meta,
103
105
  bubbles: !0,
104
106
  cancelable: !0
105
107
  });
106
- document.dispatchEvent(n);
108
+ document.dispatchEvent(e);
107
109
  }
108
- function _(t = 800, e = 600) {
109
- const n = document.createElement("canvas");
110
- return n.width = t, n.height = e, n;
110
+ function Y(t = 800, n = 600) {
111
+ const e = document.createElement("canvas");
112
+ return e.width = t, e.height = n, e;
111
113
  }
112
- function B(t, e) {
113
- Object.entries(e).forEach(([n, r]) => {
114
- if (t[n] !== r)
114
+ function q(t, n) {
115
+ Object.entries(n).forEach(([e, r]) => {
116
+ if (t[e] !== r)
115
117
  throw new Error(
116
- `Expected element.${n} to be ${r}, got ${t[n]}`
118
+ `Expected element.${e} to be ${r}, got ${t[e]}`
117
119
  );
118
120
  });
119
121
  }
120
- function K(t) {
121
- const e = t.getBoundingBox();
122
+ function H(t) {
123
+ const n = t.getBoundingBox();
124
+ return {
125
+ x: n.x,
126
+ y: n.y,
127
+ width: n.width,
128
+ height: n.height
129
+ };
130
+ }
131
+ function N(t) {
132
+ let n = !1;
133
+ const e = t.elements.map((r) => {
134
+ const i = r.name, o = r.text;
135
+ return !i || typeof o != "string" ? r : (n = !0, { ...r, text: `ASSERT_RENDERS_${i.toUpperCase()}` });
136
+ });
137
+ if (!n)
138
+ throw new m(
139
+ "E_NO_MUTABLE_BINDING",
140
+ 'this design has no NAMED text layer to mutate for the two-render check. Name one (design().text("…", { name: "headline" })) or pass opts.mutate to define the mutation.',
141
+ { docSlug: "embed-mode" }
142
+ );
143
+ return { ...t, elements: e };
144
+ }
145
+ function M(t, n) {
146
+ if (t.byteLength !== n.byteLength) return !1;
147
+ for (let e = 0; e < t.byteLength; e++)
148
+ if (t[e] !== n[e]) return !1;
149
+ return !0;
150
+ }
151
+ async function E(t, n, e, r, i) {
152
+ const o = t();
153
+ try {
154
+ return await new Promise((l, a) => {
155
+ const c = setTimeout(() => {
156
+ a(
157
+ new m("E_RENDER_FAILED", `the ${i} render produced no mockup within ${r}ms.`, {
158
+ phase: i,
159
+ timeoutMs: r
160
+ })
161
+ );
162
+ }, r);
163
+ o.onMockups((s) => {
164
+ const d = s.find((h) => h.imageUrl);
165
+ d && (clearTimeout(c), l(d));
166
+ }), o.onError((s) => {
167
+ clearTimeout(c), a(new m("E_RENDER_FAILED", `the ${i} render failed: ${s}`, { phase: i, error: s }));
168
+ }), o.renderState(n, I(e)).catch((s) => {
169
+ clearTimeout(c);
170
+ const d = s instanceof Error ? s.message : String(s);
171
+ a(new m("E_RENDER_FAILED", `the ${i} render failed to dispatch: ${d}`, { phase: i, error: d }));
172
+ });
173
+ });
174
+ } finally {
175
+ o.close();
176
+ }
177
+ }
178
+ async function w(t, n, e) {
179
+ try {
180
+ const r = await S(t, {
181
+ ...e.fetchImpl ? { fetchImpl: e.fetchImpl } : {}
182
+ });
183
+ return { bytes: r.bytes, kind: r.kind };
184
+ } catch (r) {
185
+ throw b(r) ? r.code === "ASSET_FORBIDDEN" ? new m(
186
+ "E_ASSET_FORBIDDEN",
187
+ `the shop's asset-origin allowlist denied an asset in this design (${n} render). Allow the asset's origin on the shop (or host it on the shop's storage origin).`,
188
+ { phase: n, url: t, failedAssets: r.data.failedAssets, docSlug: "embed-mode" }
189
+ ) : r.code === "SHOP_NOT_PROPAGATED" ? r : new m("E_RENDER_FAILED", `the ${n} render's image fetch failed: ${r.message}`, {
190
+ phase: n,
191
+ url: t,
192
+ ...r.data
193
+ }) : r;
194
+ }
195
+ }
196
+ async function X(t, n, e) {
197
+ const r = e.timeoutMs ?? 3e4, i = t.toJSON(), o = i.activeArtboard, l = (e.mutate ?? N)(i), a = e.grant, c = typeof a == "function" ? a : a ? () => Promise.resolve(a) : () => R({
198
+ shop: e.shop,
199
+ ...e.apiKey ? { apiKey: e.apiKey } : {},
200
+ ...e.grantBase ? { base: e.grantBase } : {},
201
+ ...e.fetchImpl ? { fetch: e.fetchImpl } : {}
202
+ }), s = () => new _({
203
+ shop: e.shop,
204
+ product: n,
205
+ ...e.wsUrl ? { wsUrl: e.wsUrl } : {},
206
+ getToken: c
207
+ }), d = await E(s, o, i, r, "seed"), h = await w(d.imageUrl, "seed", e), g = await E(s, o, l, r, "mutated"), f = await w(g.imageUrl, "mutated", e);
208
+ if (M(h.bytes, f.bytes))
209
+ throw new m(
210
+ "E_BINDING_INERT",
211
+ "the mutated render is byte-identical to the seed render — the mutation never reached the server, so the binding under test is DEAD. Usual causes: the mutated layer name doesn't exist in the design (check design.layer() names), or opts.mutate returned the seed unchanged.",
212
+ {
213
+ seedUrl: d.imageUrl,
214
+ mutatedUrl: g.imageUrl,
215
+ bytes: h.bytes.byteLength,
216
+ docSlug: "embed-mode"
217
+ }
218
+ );
122
219
  return {
123
- x: e.x,
124
- y: e.y,
125
- width: e.width,
126
- height: e.height
220
+ seed: { url: d.imageUrl, bytes: h.bytes.byteLength, kind: h.kind },
221
+ mutated: { url: g.imageUrl, bytes: f.bytes.byteLength, kind: f.kind }
127
222
  };
128
223
  }
224
+ const v = "https://assets.test-403.invalid/denied.png";
225
+ function Z() {
226
+ return A({ name: "Front", width: 1e3, height: 1e3 }).background(v, { aspectRatio: 1, name: "art" }).text("ALLOWLIST DENIAL FIXTURE", { anchor: "top", name: "headline" });
227
+ }
228
+ function ee() {
229
+ return A({ name: "Front", width: 1e3, height: 1e3 }).text("YOUR NAME", { anchor: "center", name: "name" });
230
+ }
231
+ function te(t = "failing-grant fixture: grant endpoint unavailable") {
232
+ return () => Promise.reject(new Error(t));
233
+ }
129
234
  export {
130
- M as MockEditorProvider,
131
- S as MockEditorProviderWithState,
132
- B as assertElementProperties,
133
- v as createMockArtboard,
134
- _ as createMockCanvas,
135
- p as createMockElement,
136
- k as createMockImageElement,
137
- U as expectNoEditorErrors,
138
- I as getContainedEditorErrors,
139
- K as getElementBounds,
140
- R as resetContainedEditorErrors,
141
- T as simulateKeyPress,
142
- A as waitFor,
143
- C as waitForExport
235
+ G as MockEditorProvider,
236
+ J as MockEditorProviderWithState,
237
+ v as TEST_403_ASSET_URL,
238
+ q as assertElementProperties,
239
+ X as assertRenders,
240
+ z as createMockArtboard,
241
+ Y as createMockCanvas,
242
+ W as createMockElement,
243
+ j as createMockImageElement,
244
+ ie as expectNoEditorErrors,
245
+ te as failingGrant,
246
+ oe as getContainedEditorErrors,
247
+ H as getElementBounds,
248
+ N as mutateNamedTextLayers,
249
+ ae as resetContainedEditorErrors,
250
+ V as simulateKeyPress,
251
+ Z as test403Design,
252
+ ee as testIncompleteDesign,
253
+ U as waitFor,
254
+ Q as waitForExport
144
255
  };
145
256
  //# sourceMappingURL=testing.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","sources":["../src/testing/MockEditorProvider.tsx","../src/testing/utils.ts"],"sourcesContent":["/**\n * MockEditorProvider - Lightweight mock for testing custom components\n * Compatible with Vitest (does not require Jest)\n *\n * @example\n * ```tsx\n * import { render } from '@testing-library/react';\n * import { MockEditorProvider } from '@snowcone-app/canvas/embed';\n *\n * test('my component renders', () => {\n * render(\n * <MockEditorProvider>\n * <MyCustomComponent />\n * </MockEditorProvider>\n * );\n * });\n * ```\n */\n\nimport React, { useState, ReactNode } from 'react';\nimport { EditorProvider } from '../contexts/EditorContext.js';\nimport { ThemeProvider } from '../contexts/ThemeContext.js';\nimport { ArtboardManager } from '../core/ArtboardManager.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\n\nexport type MockEditorElement = TextElement | ImageElement | GroupElement | ShapeElement;\n\nexport interface MockEditorProviderProps {\n children: ReactNode;\n\n /**\n * Initial elements to populate the editor\n */\n initialElements?: MockEditorElement[];\n\n /**\n * Initial artboards (default: one artboard)\n */\n initialArtboards?: ArtboardElement[];\n\n /**\n * Initial selection\n */\n initialSelection?: string | null;\n\n /**\n * Initial multi-selection\n */\n initialMultiSelection?: string[];\n\n /**\n * Initial artboard configuration\n */\n initialArtboardConfig?: {\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n };\n}\n\n/**\n * MockEditorProvider - Wraps EditorProvider with test-friendly defaults\n *\n * This is a lightweight wrapper that provides the same EditorContext\n * as the real app, making it perfect for testing custom components.\n */\nexport const MockEditorProvider: React.FC<MockEditorProviderProps> = ({\n children,\n initialElements: _initialElements = [],\n initialArtboards,\n initialSelection: _initialSelection = null,\n initialMultiSelection: _initialMultiSelection = [],\n initialArtboardConfig = {},\n}) => {\n // Create artboard manager if artboards provided\n const [_artboardManager] = useState(() => {\n const manager = new ArtboardManager();\n\n if (initialArtboards && initialArtboards.length > 0) {\n // Use provided artboards\n initialArtboards.forEach(artboard => {\n manager.createArtboard({\n name: artboard.name,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n });\n });\n } else {\n // Create default artboard\n manager.createArtboard({\n name: 'Test Artboard',\n width: 1200,\n height: 1200,\n backgroundColor: '#FFFFFF',\n ...initialArtboardConfig,\n });\n }\n\n return manager;\n });\n\n // Simple wrapper that passes children through to real EditorProvider\n // The EditorProvider will handle all the state management\n // ThemeProvider is required because CanvasEditor uses useTheme()\n return (\n <ThemeProvider>\n <EditorProvider\n initialArtboardConfig={initialArtboardConfig}\n >\n {children}\n </EditorProvider>\n </ThemeProvider>\n );\n};\n\n/**\n * MockEditorProviderWithState - For tests that need to control state directly\n *\n * Use this when you need fine-grained control over the editor state.\n * Most tests should use the simpler MockEditorProvider above.\n */\nexport const MockEditorProviderWithState: React.FC<MockEditorProviderProps> = ({\n children,\n initialElements: _initialElements = [],\n initialSelection: _initialSelection = null,\n initialMultiSelection: _initialMultiSelection = [],\n initialArtboardConfig = {},\n}) => {\n return (\n <ThemeProvider>\n <EditorProvider initialArtboardConfig={initialArtboardConfig}>\n {children}\n </EditorProvider>\n </ThemeProvider>\n );\n};\n","/**\n * Testing Utilities - Helpers for creating mock elements and artboards\n * Compatible with Vitest\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport { getTransformById } from '../transforms/registry.js';\nimport type { TransformType, ImageElementConfig } from '../types/index.js';\n\n/**\n * Create a mock text element with sensible defaults\n *\n * @example\n * ```tsx\n * const element = createMockElement('custom', {\n * text: 'Hello World',\n * fontSize: 48,\n * fontColor: '#FF0000'\n * });\n * ```\n */\nexport function createMockElement(\n transformType: TransformType = 'custom',\n overrides: Partial<TextElement> = {}\n): TextElement {\n // Get the correct concrete class from registry\n const transformDef = getTransformById(transformType);\n if (!transformDef || !transformDef.Component) {\n throw new Error(`Transform type \"${transformType}\" not found in registry`);\n }\n\n // Create element using the concrete class\n const element = new transformDef.Component({\n transformType,\n text: 'Test Text',\n fontSize: 32,\n color: '#000000',\n fontFamily: 'Arial',\n x: 100,\n y: 100,\n rotation: 0,\n ...overrides,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- test utility: overrides merge Partial<TextElement> properties with config, which are structurally compatible but not type-assignable\n } as any);\n\n return element as TextElement;\n}\n\n/**\n * Create a mock image element\n *\n * @example\n * ```tsx\n * const image = createMockImageElement({\n * imageUrl: 'data:image/png;base64,...',\n * width: 200,\n * height: 200\n * });\n * ```\n */\nexport function createMockImageElement(\n overrides: Partial<ImageElementConfig> = {}\n): ImageElement {\n const element = new ImageElement({\n imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',\n imageAspectRatio: 1,\n x: 100,\n y: 100,\n rotation: 0,\n ...overrides,\n });\n\n return element;\n}\n\n/**\n * Create a mock artboard\n *\n * @example\n * ```tsx\n * const artboard = createMockArtboard({\n * name: 'Front',\n * width: 1200,\n * height: 1200,\n * backgroundColor: '#FFFFFF'\n * });\n * ```\n */\nexport function createMockArtboard(\n overrides: Partial<{\n name: string;\n width: number;\n height: number;\n x: number;\n y: number;\n backgroundColor: string;\n }> = {}\n): ArtboardElement {\n const artboard = new ArtboardElement({\n name: 'Test Artboard',\n width: 1200,\n height: 1200,\n x: 0,\n y: 0,\n backgroundColor: '#FFFFFF',\n ...overrides,\n });\n\n return artboard;\n}\n\n/**\n * Wait for a condition to become true\n * Useful for async testing\n *\n * @example\n * ```tsx\n * await waitFor(() => screen.getByText('Loaded'), 5000);\n * ```\n */\nexport async function waitFor(\n condition: () => boolean,\n timeout = 5000,\n interval = 100\n): Promise<void> {\n const startTime = Date.now();\n\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for condition after ${timeout}ms`);\n }\n await new Promise(resolve => setTimeout(resolve, interval));\n }\n}\n\n/**\n * Wait for an export to complete\n * Polls until dataUrl is available\n *\n * @example\n * ```tsx\n * const dataUrl = await waitForExport('artboard-1', 5000);\n * expect(dataUrl).toContain('data:image/png');\n * ```\n */\nexport async function waitForExport(\n _artboardId: string,\n timeout = 5000\n): Promise<string | null> {\n let dataUrl: string | null = null;\n\n await waitFor(() => {\n // Check if export completed (this would need to be implemented based on your export state)\n // For now, just return false as a placeholder\n return false;\n }, timeout);\n\n return dataUrl;\n}\n\n/**\n * Simulate a keyboard event\n * Useful for testing keyboard shortcuts\n *\n * @example\n * ```tsx\n * simulateKeyPress('c', { meta: true }); // Cmd+C\n * ```\n */\nexport function simulateKeyPress(\n key: string,\n options: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n } = {}\n): void {\n const event = new KeyboardEvent('keydown', {\n key,\n ctrlKey: options.ctrl,\n shiftKey: options.shift,\n altKey: options.alt,\n metaKey: options.meta,\n bubbles: true,\n cancelable: true,\n });\n\n document.dispatchEvent(event);\n}\n\n/**\n * Create a mock canvas element for testing\n * Returns an HTMLCanvasElement with mocked context\n *\n * @example\n * ```tsx\n * const canvas = createMockCanvas(800, 600);\n * const ctx = canvas.getContext('2d');\n * ```\n */\nexport function createMockCanvas(\n width = 800,\n height = 600\n): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n return canvas;\n}\n\n/**\n * Assert that an element has certain properties\n * Useful for programmatic assertions\n *\n * @example\n * ```tsx\n * assertElementProperties(element, {\n * text: 'Hello',\n * fontSize: 48,\n * fontColor: '#FF0000'\n * });\n * ```\n */\nexport function assertElementProperties(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Test utility: accepts any element type and checks arbitrary properties\n element: any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Test utility: expected values can be any type\n expected: Record<string, any>\n): void {\n Object.entries(expected).forEach(([key, value]) => {\n if (element[key] !== value) {\n throw new Error(\n `Expected element.${key} to be ${value}, got ${element[key]}`\n );\n }\n });\n}\n\n/**\n * Get element bounding box for testing\n * Returns computed bounds from the element\n *\n * @example\n * ```tsx\n * const bounds = getElementBounds(element);\n * expect(bounds.width).toBeGreaterThan(0);\n * ```\n */\nexport function getElementBounds(element: TextElement | ImageElement): {\n x: number;\n y: number;\n width: number;\n height: number;\n} {\n const bbox = element.getBoundingBox();\n\n return {\n x: bbox.x,\n y: bbox.y,\n width: bbox.width,\n height: bbox.height,\n };\n}\n"],"names":["MockEditorProvider","children","_initialElements","initialArtboards","_initialSelection","_initialMultiSelection","initialArtboardConfig","_artboardManager","useState","manager","ArtboardManager","artboard","ThemeProvider","jsx","EditorProvider","MockEditorProviderWithState","createMockElement","transformType","overrides","transformDef","getTransformById","createMockImageElement","ImageElement","createMockArtboard","ArtboardElement","waitFor","condition","timeout","interval","startTime","resolve","waitForExport","_artboardId","dataUrl","simulateKeyPress","key","options","event","createMockCanvas","width","height","canvas","assertElementProperties","element","expected","value","getElementBounds","bbox"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","sources":["../src/testing/MockEditorProvider.tsx","../src/testing/utils.ts","../src/testing/assertRenders.ts","../src/testing/failureFixtures.ts"],"sourcesContent":["/**\n * MockEditorProvider - Lightweight mock for testing custom components\n * Compatible with Vitest (does not require Jest)\n *\n * @example\n * ```tsx\n * import { render } from '@testing-library/react';\n * import { MockEditorProvider } from '@snowcone-app/canvas/embed';\n *\n * test('my component renders', () => {\n * render(\n * <MockEditorProvider>\n * <MyCustomComponent />\n * </MockEditorProvider>\n * );\n * });\n * ```\n */\n\nimport React, { useState, ReactNode } from 'react';\nimport { EditorProvider } from '../contexts/EditorContext.js';\nimport { ThemeProvider } from '../contexts/ThemeContext.js';\nimport { ArtboardManager } from '../core/ArtboardManager.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport type { TextElement } from '../core/TextElement.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { GroupElement } from '../core/GroupElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\n\nexport type MockEditorElement = TextElement | ImageElement | GroupElement | ShapeElement;\n\nexport interface MockEditorProviderProps {\n children: ReactNode;\n\n /**\n * Initial elements to populate the editor\n */\n initialElements?: MockEditorElement[];\n\n /**\n * Initial artboards (default: one artboard)\n */\n initialArtboards?: ArtboardElement[];\n\n /**\n * Initial selection\n */\n initialSelection?: string | null;\n\n /**\n * Initial multi-selection\n */\n initialMultiSelection?: string[];\n\n /**\n * Initial artboard configuration\n */\n initialArtboardConfig?: {\n name?: string;\n width?: number;\n height?: number;\n backgroundColor?: string;\n };\n}\n\n/**\n * MockEditorProvider - Wraps EditorProvider with test-friendly defaults\n *\n * This is a lightweight wrapper that provides the same EditorContext\n * as the real app, making it perfect for testing custom components.\n */\nexport const MockEditorProvider: React.FC<MockEditorProviderProps> = ({\n children,\n initialElements: _initialElements = [],\n initialArtboards,\n initialSelection: _initialSelection = null,\n initialMultiSelection: _initialMultiSelection = [],\n initialArtboardConfig = {},\n}) => {\n // Create artboard manager if artboards provided\n const [_artboardManager] = useState(() => {\n const manager = new ArtboardManager();\n\n if (initialArtboards && initialArtboards.length > 0) {\n // Use provided artboards\n initialArtboards.forEach(artboard => {\n manager.createArtboard({\n name: artboard.name,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n });\n });\n } else {\n // Create default artboard\n manager.createArtboard({\n name: 'Test Artboard',\n width: 1200,\n height: 1200,\n backgroundColor: '#FFFFFF',\n ...initialArtboardConfig,\n });\n }\n\n return manager;\n });\n\n // Simple wrapper that passes children through to real EditorProvider\n // The EditorProvider will handle all the state management\n // ThemeProvider is required because CanvasEditor uses useTheme()\n return (\n <ThemeProvider>\n <EditorProvider\n initialArtboardConfig={initialArtboardConfig}\n >\n {children}\n </EditorProvider>\n </ThemeProvider>\n );\n};\n\n/**\n * MockEditorProviderWithState - For tests that need to control state directly\n *\n * Use this when you need fine-grained control over the editor state.\n * Most tests should use the simpler MockEditorProvider above.\n */\nexport const MockEditorProviderWithState: React.FC<MockEditorProviderProps> = ({\n children,\n initialElements: _initialElements = [],\n initialSelection: _initialSelection = null,\n initialMultiSelection: _initialMultiSelection = [],\n initialArtboardConfig = {},\n}) => {\n return (\n <ThemeProvider>\n <EditorProvider initialArtboardConfig={initialArtboardConfig}>\n {children}\n </EditorProvider>\n </ThemeProvider>\n );\n};\n","/**\n * Testing Utilities - Helpers for creating mock elements and artboards\n * Compatible with Vitest\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { ArtboardElement } from '../core/ArtboardElement.js';\nimport { getTransformById } from '../transforms/registry.js';\nimport type { TransformType, ImageElementConfig } from '../types/index.js';\n\n/**\n * Create a mock text element with sensible defaults\n *\n * @example\n * ```tsx\n * const element = createMockElement('custom', {\n * text: 'Hello World',\n * fontSize: 48,\n * fontColor: '#FF0000'\n * });\n * ```\n */\nexport function createMockElement(\n transformType: TransformType = 'custom',\n overrides: Partial<TextElement> = {}\n): TextElement {\n // Get the correct concrete class from registry\n const transformDef = getTransformById(transformType);\n if (!transformDef || !transformDef.Component) {\n throw new Error(`Transform type \"${transformType}\" not found in registry`);\n }\n\n // Create element using the concrete class\n const element = new transformDef.Component({\n transformType,\n text: 'Test Text',\n fontSize: 32,\n color: '#000000',\n fontFamily: 'Arial',\n x: 100,\n y: 100,\n rotation: 0,\n ...overrides,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- test utility: overrides merge Partial<TextElement> properties with config, which are structurally compatible but not type-assignable\n } as any);\n\n return element as TextElement;\n}\n\n/**\n * Create a mock image element\n *\n * @example\n * ```tsx\n * const image = createMockImageElement({\n * imageUrl: 'data:image/png;base64,...',\n * width: 200,\n * height: 200\n * });\n * ```\n */\nexport function createMockImageElement(\n overrides: Partial<ImageElementConfig> = {}\n): ImageElement {\n const element = new ImageElement({\n imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',\n imageAspectRatio: 1,\n x: 100,\n y: 100,\n rotation: 0,\n ...overrides,\n });\n\n return element;\n}\n\n/**\n * Create a mock artboard\n *\n * @example\n * ```tsx\n * const artboard = createMockArtboard({\n * name: 'Front',\n * width: 1200,\n * height: 1200,\n * backgroundColor: '#FFFFFF'\n * });\n * ```\n */\nexport function createMockArtboard(\n overrides: Partial<{\n name: string;\n width: number;\n height: number;\n x: number;\n y: number;\n backgroundColor: string;\n }> = {}\n): ArtboardElement {\n const artboard = new ArtboardElement({\n name: 'Test Artboard',\n width: 1200,\n height: 1200,\n x: 0,\n y: 0,\n backgroundColor: '#FFFFFF',\n ...overrides,\n });\n\n return artboard;\n}\n\n/**\n * Wait for a condition to become true\n * Useful for async testing\n *\n * @example\n * ```tsx\n * await waitFor(() => screen.getByText('Loaded'), 5000);\n * ```\n */\nexport async function waitFor(\n condition: () => boolean,\n timeout = 5000,\n interval = 100\n): Promise<void> {\n const startTime = Date.now();\n\n while (!condition()) {\n if (Date.now() - startTime > timeout) {\n throw new Error(`Timeout waiting for condition after ${timeout}ms`);\n }\n await new Promise(resolve => setTimeout(resolve, interval));\n }\n}\n\n/**\n * Wait for an export to complete\n * Polls until dataUrl is available\n *\n * @example\n * ```tsx\n * const dataUrl = await waitForExport('artboard-1', 5000);\n * expect(dataUrl).toContain('data:image/png');\n * ```\n */\nexport async function waitForExport(\n _artboardId: string,\n timeout = 5000\n): Promise<string | null> {\n let dataUrl: string | null = null;\n\n await waitFor(() => {\n // Check if export completed (this would need to be implemented based on your export state)\n // For now, just return false as a placeholder\n return false;\n }, timeout);\n\n return dataUrl;\n}\n\n/**\n * Simulate a keyboard event\n * Useful for testing keyboard shortcuts\n *\n * @example\n * ```tsx\n * simulateKeyPress('c', { meta: true }); // Cmd+C\n * ```\n */\nexport function simulateKeyPress(\n key: string,\n options: {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n } = {}\n): void {\n const event = new KeyboardEvent('keydown', {\n key,\n ctrlKey: options.ctrl,\n shiftKey: options.shift,\n altKey: options.alt,\n metaKey: options.meta,\n bubbles: true,\n cancelable: true,\n });\n\n document.dispatchEvent(event);\n}\n\n/**\n * Create a mock canvas element for testing\n * Returns an HTMLCanvasElement with mocked context\n *\n * @example\n * ```tsx\n * const canvas = createMockCanvas(800, 600);\n * const ctx = canvas.getContext('2d');\n * ```\n */\nexport function createMockCanvas(\n width = 800,\n height = 600\n): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n\n return canvas;\n}\n\n/**\n * Assert that an element has certain properties\n * Useful for programmatic assertions\n *\n * @example\n * ```tsx\n * assertElementProperties(element, {\n * text: 'Hello',\n * fontSize: 48,\n * fontColor: '#FF0000'\n * });\n * ```\n */\nexport function assertElementProperties(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Test utility: accepts any element type and checks arbitrary properties\n element: any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Test utility: expected values can be any type\n expected: Record<string, any>\n): void {\n Object.entries(expected).forEach(([key, value]) => {\n if (element[key] !== value) {\n throw new Error(\n `Expected element.${key} to be ${value}, got ${element[key]}`\n );\n }\n });\n}\n\n/**\n * Get element bounding box for testing\n * Returns computed bounds from the element\n *\n * @example\n * ```tsx\n * const bounds = getElementBounds(element);\n * expect(bounds.width).toBeGreaterThan(0);\n * ```\n */\nexport function getElementBounds(element: TextElement | ImageElement): {\n x: number;\n y: number;\n width: number;\n height: number;\n} {\n const bbox = element.getBoundingBox();\n\n return {\n x: bbox.x,\n y: bbox.y,\n width: bbox.width,\n height: bbox.height,\n };\n}\n","/**\n * `assertRenders(design, product, opts)` — the TWO-RENDER live contract check\n * (agent-DX decision F4, ADR-0085 plan).\n *\n * WHY TWO RENDERS: \"the URL returns an image\" can't catch a DEAD BINDING — a\n * wrong layer name still renders the seed beautifully. So this renders the\n * design twice: once as seeded, once with the personalization bindings\n * mutated, and asserts the encoded image BYTES differ (mockup delivery is\n * deterministic per state, so byte-identical means pixel-identical means the\n * mutation never reached the render).\n *\n * What it proves, in order:\n * 1. the seed design renders: HTTP 200 + real raster bytes (non-blank);\n * 2. the mutated design renders the same way;\n * 3. the two renders DIFFER — the binding is alive.\n *\n * Failures are the structured error contract (E3), never prose-only:\n * - `E_ASSET_FORBIDDEN` — the shop's asset-origin allowlist denied a design\n * asset (translated from the server's 403; `data.failedAssets` names it);\n * - `E_RENDER_FAILED` — render error / timeout / non-image response;\n * - `E_BINDING_INERT` — both renders byte-identical (dead binding);\n * - `E_NO_MUTABLE_BINDING` — nothing to mutate and no `mutate` given.\n * - The SDK's `SHOP_NOT_PROPAGATED` passes through: a freshly minted shop\n * that never becomes visible is an environment fault, named as such.\n *\n * This talks to the REAL realtime render path (network). Use it in nightly /\n * acceptance jobs and agent verify loops — per-PR unit tests should mock the\n * SDK transport instead (plan: per-PR replay, nightly network-real).\n */\n\nimport {\n RenderSession,\n fetchMockupImage,\n mintRealtimeGrant,\n type MockupResult,\n type RealtimeGrant,\n type RenderProduct,\n} from '@snowcone-app/sdk';\nimport { isSnowconeSdkError } from '@snowcone-app/sdk';\nimport { serializeStateForServer } from '../rendering/serialize-for-server.js';\nimport { EditorError } from '../errors/editorError.js';\nimport type { DesignState } from '../components/editor/designStateStore.js';\n\n/** Anything `design()` produces (or a compatible shape). */\nexport interface RenderableDesign {\n toJSON(): DesignState;\n}\n\nexport interface AssertRendersOptions {\n /** Publishable shop id (= shop.id). */\n shop: string;\n /** Secret API key (sk_) for the grant; omit for the keyless publishable\n * path (sandbox/trial shops — the F1 bootstrap default). */\n apiKey?: string;\n /** Pre-fetched grant or grant factory — overrides the mint entirely. */\n grant?: RealtimeGrant | (() => Promise<RealtimeGrant>);\n /** Backend base for the keyless/sk grant mint (defaults to prod). */\n grantBase?: string;\n /** Override the realtime WS endpoint. */\n wsUrl?: string;\n /**\n * Produce the mutated state for the second render. Default: rewrite the\n * text of EVERY named text layer to a sentinel (`ASSERT_RENDERS_<name>`).\n * Throws `E_NO_MUTABLE_BINDING` if the design has no named text layer and\n * no override is given.\n */\n mutate?: (seed: DesignState) => DesignState;\n /** Per-render timeout (ms). Default 30_000. */\n timeoutMs?: number;\n /** Override fetch for the image downloads (tests). */\n fetchImpl?: typeof fetch;\n}\n\nexport interface AssertRendersResult {\n /** The seed render's mockup URL + encoded size. */\n seed: { url: string; bytes: number; kind: string };\n /** The mutated render's mockup URL + encoded size. */\n mutated: { url: string; bytes: number; kind: string };\n}\n\n/** Default mutation: every NAMED text layer gets a sentinel value. */\nexport function mutateNamedTextLayers(seed: DesignState): DesignState {\n let mutatedAny = false;\n const elements = seed.elements.map((el) => {\n const name = (el as { name?: string }).name;\n const text = (el as { text?: string }).text;\n if (!name || typeof text !== 'string') return el;\n mutatedAny = true;\n return { ...el, text: `ASSERT_RENDERS_${name.toUpperCase()}` } as typeof el;\n });\n if (!mutatedAny) {\n throw new EditorError(\n 'E_NO_MUTABLE_BINDING',\n 'this design has no NAMED text layer to mutate for the two-render check. Name one ' +\n '(design().text(\"…\", { name: \"headline\" })) or pass opts.mutate to define the mutation.',\n { docSlug: 'embed-mode' },\n );\n }\n return { ...seed, elements };\n}\n\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) return false;\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * Render one state on a FRESH session and resolve the first mockup result.\n * One session per render on purpose: a session keeps emitting trailing mockup\n * results (multi-mockup products emit per scene), so reusing it for the\n * second render could resolve the mutated phase with a stale seed emission —\n * and with content-addressed delivery a dead binding makes the two\n * indistinguishable by URL. A fresh session has no history to confuse.\n */\nasync function renderOnce(\n makeSession: () => RenderSession,\n placement: string,\n state: DesignState,\n timeoutMs: number,\n label: 'seed' | 'mutated',\n): Promise<MockupResult> {\n const session = makeSession();\n try {\n return await new Promise<MockupResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new EditorError('E_RENDER_FAILED', `the ${label} render produced no mockup within ${timeoutMs}ms.`, {\n phase: label,\n timeoutMs,\n }),\n );\n }, timeoutMs);\n session.onMockups((results) => {\n const withImage = results.find((r) => r.imageUrl);\n if (!withImage) return;\n clearTimeout(timer);\n resolve(withImage);\n });\n session.onError((message) => {\n clearTimeout(timer);\n reject(new EditorError('E_RENDER_FAILED', `the ${label} render failed: ${message}`, { phase: label, error: message }));\n });\n void session.renderState(placement, serializeStateForServer(state)).catch((err) => {\n clearTimeout(timer);\n const message = err instanceof Error ? err.message : String(err);\n reject(new EditorError('E_RENDER_FAILED', `the ${label} render failed to dispatch: ${message}`, { phase: label, error: message }));\n });\n });\n } finally {\n session.close();\n }\n}\n\n/** Fetch + validate one rendered image, translating SDK failures (asset-403 →\n * `E_ASSET_FORBIDDEN`) into the editor error contract. */\nasync function fetchRender(url: string, label: 'seed' | 'mutated', opts: AssertRendersOptions): Promise<{ bytes: Uint8Array; kind: string }> {\n try {\n const img = await fetchMockupImage(url, {\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n return { bytes: img.bytes, kind: img.kind };\n } catch (err) {\n if (isSnowconeSdkError(err)) {\n if (err.code === 'ASSET_FORBIDDEN') {\n throw new EditorError(\n 'E_ASSET_FORBIDDEN',\n `the shop's asset-origin allowlist denied an asset in this design (${label} render). ` +\n `Allow the asset's origin on the shop (or host it on the shop's storage origin).`,\n { phase: label, url, failedAssets: err.data.failedAssets, docSlug: 'embed-mode' },\n );\n }\n if (err.code === 'SHOP_NOT_PROPAGATED') throw err; // already structured + named\n throw new EditorError('E_RENDER_FAILED', `the ${label} render's image fetch failed: ${err.message}`, {\n phase: label,\n url,\n ...err.data,\n });\n }\n throw err;\n }\n}\n\n/**\n * Prove a design + product render AND that the design's bindings are alive\n * (two-render contract). See the module doc for the failure catalog.\n *\n * @experimental ADR-0085; subject to change until the P4 gate.\n */\nexport async function assertRenders(\n design: RenderableDesign,\n product: RenderProduct,\n opts: AssertRendersOptions,\n): Promise<AssertRendersResult> {\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const seed = design.toJSON();\n const placement = seed.activeArtboard;\n const mutated = (opts.mutate ?? mutateNamedTextLayers)(seed);\n\n const grant = opts.grant;\n const getToken =\n typeof grant === 'function'\n ? grant\n : grant\n ? () => Promise.resolve(grant)\n : () =>\n mintRealtimeGrant({\n shop: opts.shop,\n ...(opts.apiKey ? { apiKey: opts.apiKey } : {}),\n ...(opts.grantBase ? { base: opts.grantBase } : {}),\n ...(opts.fetchImpl ? { fetch: opts.fetchImpl } : {}),\n });\n\n const makeSession = () =>\n new RenderSession({\n shop: opts.shop,\n product,\n ...(opts.wsUrl ? { wsUrl: opts.wsUrl } : {}),\n getToken,\n });\n\n const seedResult = await renderOnce(makeSession, placement, seed, timeoutMs, 'seed');\n const seedImage = await fetchRender(seedResult.imageUrl!, 'seed', opts);\n\n const mutatedResult = await renderOnce(makeSession, placement, mutated, timeoutMs, 'mutated');\n const mutatedImage = await fetchRender(mutatedResult.imageUrl!, 'mutated', opts);\n\n if (bytesEqual(seedImage.bytes, mutatedImage.bytes)) {\n throw new EditorError(\n 'E_BINDING_INERT',\n 'the mutated render is byte-identical to the seed render — the mutation never reached ' +\n 'the server, so the binding under test is DEAD. Usual causes: the mutated layer name ' +\n \"doesn't exist in the design (check design.layer() names), or opts.mutate returned the \" +\n 'seed unchanged.',\n {\n seedUrl: seedResult.imageUrl,\n mutatedUrl: mutatedResult.imageUrl,\n bytes: seedImage.bytes.byteLength,\n docSlug: 'embed-mode',\n },\n );\n }\n\n return {\n seed: { url: seedResult.imageUrl!, bytes: seedImage.bytes.byteLength, kind: seedImage.kind },\n mutated: { url: mutatedResult.imageUrl!, bytes: mutatedImage.bytes.byteLength, kind: mutatedImage.kind },\n };\n}\n","/**\n * Deterministic failure-mode fixtures (ADR-0085 plan, P2 — the magic-test-card\n * equivalent): trigger each commerce/render failure ON DEMAND so integrators\n * build their error UI against real failures instead of waiting for prod to\n * produce one.\n *\n * These are the CLIENT-ACHIEVABLE fixtures — each works against any shop\n * (including a fresh sandbox shop) with no server cooperation:\n *\n * - {@link test403Design} — asset-origin allowlist denial. References an\n * asset on an origin no shop allowlists, so the server's asset fetch is\n * denied deterministically. Surfaces as a render error (live path) or\n * `E_ASSET_FORBIDDEN` from `assertRenders`.\n * - {@link testIncompleteDesign} — Buy completeness failure. A design whose\n * only personalization layer still carries its seed value, so\n * `<Editor.Buy>` gates with `layer-seed:<name>` (and `layer:<name>` once\n * cleared). Pair with `<Editor.TextField layer={d.layer('name')} />`.\n * - {@link failingGrant} — grant failure. A grant factory that always\n * rejects; pass as `<Editor.Root grant={failingGrant()}>` to exercise the\n * \"grant fetch fails\" surface (`onRenderError` + the Preview's\n * `data-sc-part=\"preview-error\"` state).\n *\n * Server-triggered fixtures (a `test-slow` product for the timeout path, a\n * forced render-error token) need render-edge cooperation and are tracked\n * separately — they are NOT silently stubbed here.\n */\n\nimport { design } from '../design/design-builder.js';\nimport type { RealtimeGrant } from '@snowcone-app/sdk';\n\n/**\n * An origin that is deliberately on NO shop's asset-origin allowlist. The\n * `.invalid` TLD is reserved (RFC 2606) — it can never resolve, and the\n * allowlist check denies it before any fetch is attempted, so the failure is\n * deterministic and free.\n */\nexport const TEST_403_ASSET_URL = 'https://assets.test-403.invalid/denied.png';\n\n/**\n * A design whose background asset the shop's allowlist is guaranteed to deny —\n * the deterministic allowlist-denial (403) trigger.\n */\nexport function test403Design() {\n return design({ name: 'Front', width: 1000, height: 1000 })\n .background(TEST_403_ASSET_URL, { aspectRatio: 1, name: 'art' })\n .text('ALLOWLIST DENIAL FIXTURE', { anchor: 'top', name: 'headline' });\n}\n\n/**\n * A design that gates `<Editor.Buy>` out of the box: its one personalization\n * layer (`'name'`, seed `'YOUR NAME'`) is required and still equals its seed,\n * so Buy renders `data-sc-buy-state=\"incomplete\"` with\n * `data-sc-buy-missing=\"layer-seed:name\"` until a real value is typed.\n */\nexport function testIncompleteDesign() {\n return design({ name: 'Front', width: 1000, height: 1000 })\n .text('YOUR NAME', { anchor: 'center', name: 'name' });\n}\n\n/**\n * A grant factory that always rejects — the deterministic \"grant fetch\n * failed\" trigger. The rejection reason is stable for assertions.\n */\nexport function failingGrant(reason = 'failing-grant fixture: grant endpoint unavailable'): () => Promise<RealtimeGrant> {\n return () => Promise.reject(new Error(reason));\n}\n"],"names":["MockEditorProvider","children","_initialElements","initialArtboards","_initialSelection","_initialMultiSelection","initialArtboardConfig","_artboardManager","useState","manager","ArtboardManager","artboard","ThemeProvider","jsx","EditorProvider","MockEditorProviderWithState","createMockElement","transformType","overrides","transformDef","getTransformById","createMockImageElement","ImageElement","createMockArtboard","ArtboardElement","waitFor","condition","timeout","interval","startTime","resolve","waitForExport","_artboardId","dataUrl","simulateKeyPress","key","options","event","createMockCanvas","width","height","canvas","assertElementProperties","element","expected","value","getElementBounds","bbox","mutateNamedTextLayers","seed","mutatedAny","elements","el","name","text","EditorError","bytesEqual","a","b","i","renderOnce","makeSession","placement","state","timeoutMs","label","session","reject","timer","results","withImage","r","message","serializeStateForServer","err","fetchRender","url","opts","img","fetchMockupImage","isSnowconeSdkError","assertRenders","design","product","mutated","grant","getToken","mintRealtimeGrant","RenderSession","seedResult","seedImage","mutatedResult","mutatedImage","TEST_403_ASSET_URL","test403Design","testIncompleteDesign","failingGrant","reason"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/canvas",
3
- "version": "0.18.1",
3
+ "version": "0.19.1",
4
4
  "description": "Self-contained canvas editor component for e-commerce product customization",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -107,7 +107,7 @@
107
107
  "react-colorful": "^5.6.1",
108
108
  "tailwind-merge": "^3.0.0",
109
109
  "tailwind-variants": "^3.1.1",
110
- "@snowcone-app/sdk": "0.14.0"
110
+ "@snowcone-app/sdk": "0.15.0"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@axe-core/playwright": "^4.11.0",
@@ -169,7 +169,7 @@
169
169
  "vite-plugin-node-polyfills": "^0.24.0",
170
170
  "vitest": "^3.2.4",
171
171
  "zod": "^4.1.12",
172
- "@snowcone-app/ui": "0.2.4"
172
+ "@snowcone-app/ui": "0.2.5"
173
173
  },
174
174
  "scripts": {
175
175
  "dev": "vite",