@snowcone-app/ui 0.4.11 → 0.4.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/ui",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "React components for merchandise visualization and customization",
5
5
  "keywords": [
6
6
  "react",
@@ -394,8 +394,13 @@ class CanvasExportService {
394
394
  handleStateChange = (state: any): void => {
395
395
  if (!state) return;
396
396
 
397
+ // An empty design is skipped only BEFORE anything has been sent this
398
+ // session (the initial mount of a blank editor — the PDP already shows the
399
+ // blank product). Once art has rendered, an empty state is a real edit:
400
+ // deleting/undoing the last element must re-render the blank product, or
401
+ // the mockup keeps showing art that is no longer in the design.
397
402
  const hasElements = state.elements?.length > 0;
398
- if (!hasElements) return;
403
+ if (!hasElements && !this.jsonModeSentSuccessfully) return;
399
404
 
400
405
  // Don't send or cache state while any image is still loading —
401
406
  // the serializer would skip it and the server would render without artwork
@@ -507,6 +512,10 @@ class CanvasExportService {
507
512
  this.stateChangeThrottleTimeout = null;
508
513
  }
509
514
  this.stateChangeLastSendTime = 0;
515
+ // A new editor session starts with nothing sent: its first empty state
516
+ // is a blank mount again, not a deletion (see handleStateChange).
517
+ this.jsonModeSentSuccessfully = false;
518
+ this.pendingCanvasState = null;
510
519
  // Also clear ImageBitmap cache on full reset
511
520
  if (this.clearImageBitmapCache) {
512
521
  this.clearImageBitmapCache();
@@ -0,0 +1,48 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import { canvasExportService } from "../CanvasExportService";
4
+
5
+ const state = (elements: unknown[]) => ({
6
+ elements,
7
+ artboards: [{ name: "Front", width: 100, height: 100 }],
8
+ activeArtboard: "Front",
9
+ });
10
+
11
+ describe("CanvasExportService — empty designs", () => {
12
+ let sendCanvasState: ReturnType<typeof vi.fn>;
13
+
14
+ beforeEach(() => {
15
+ vi.useFakeTimers();
16
+ vi.spyOn(console, "log").mockImplementation(() => {});
17
+ canvasExportService.reset();
18
+ sendCanvasState = vi.fn(() => true);
19
+ canvasExportService.configure({
20
+ isRealtimeEnabled: true,
21
+ sendCanvasState: sendCanvasState as never,
22
+ serializeStateForServer: ((s: ReturnType<typeof state>) => s) as never,
23
+ mockupCount: 1,
24
+ placementName: "Front",
25
+ });
26
+ });
27
+ afterEach(() => {
28
+ vi.useRealTimers();
29
+ vi.restoreAllMocks();
30
+ });
31
+
32
+ it("ignores an empty design before anything was ever sent (blank editor mount)", () => {
33
+ canvasExportService.handleStateChange(state([]));
34
+ vi.runAllTimers();
35
+ expect(sendCanvasState).not.toHaveBeenCalled();
36
+ });
37
+
38
+ it("sends an empty design once art has rendered — deleting the last element re-renders the blank product", () => {
39
+ canvasExportService.handleStateChange(state([{ type: "shape" }]));
40
+ vi.runAllTimers();
41
+ expect(sendCanvasState).toHaveBeenCalledTimes(1);
42
+
43
+ canvasExportService.handleStateChange(state([]));
44
+ vi.runAllTimers();
45
+ expect(sendCanvasState).toHaveBeenCalledTimes(2);
46
+ expect((sendCanvasState.mock.calls[1][1] as { elements: unknown[] }).elements).toEqual([]);
47
+ });
48
+ });