import { describe, expect, it } from "vitest"; import { adjustBackgroundPixels, backgroundDrawPlan, CanvasEditHistory, createEditorCanvasState, cssFilterForBackground, defaultBackgroundAdjustments, deserializeFabricCanvas, switchBackground, updateBackgroundAdjustments, } from "../../apps/web/src/editor-canvas.js"; const element = { colors: ["#111111", "#222222", "#333333", "#444444", "#555555"], created_at: "2026-08-02T18:00:00.000Z", element_id: "00000000-0000-4000-8000-000000000001", opacity: 1, position: { x: 0.25, y: 0.35 }, resource_version: "palette-v1", rotation: 14, scale: { x: 1.2, y: 0.8 }, template_or_asset_id: "palette-fixture", type: "color_card" as const, z_index: 0, }; describe("TDD-WP4-BG-001/002 canvas boundary", () => { it("builds valid filters and distinct contain/crop draw plans", () => { const adjustments = { ...defaultBackgroundAdjustments(), brightness: 25, contrast: 15, saturation: -20 }; expect(cssFilterForBackground(adjustments)).toBe("brightness(125%) contrast(115%) saturate(80%)"); expect(cssFilterForBackground(defaultBackgroundAdjustments())).toBe("none"); expect(backgroundDrawPlan({ height: 200, width: 400 }, { height: 100, width: 100 }, { ...adjustments, fit: "fit" })).toEqual({ destination: { height: 50, width: 100, x: 0, y: 25 }, source: { height: 200, width: 400, x: 0, y: 0 }, }); expect(backgroundDrawPlan({ height: 200, width: 400 }, { height: 100, width: 100 }, { ...adjustments, fit: "crop" })).toEqual({ destination: { height: 100, width: 100, x: 0, y: 0 }, source: { height: 200, width: 200, x: 100, y: 0 }, }); }); it("changes pixels for temperature and sharpness without changing alpha", () => { const flat = new Uint8ClampedArray([100, 100, 100, 255]); expect([...adjustBackgroundPixels(flat, 1, 1, { sharpness: 0, temperature: 100 })]).toEqual([135, 108, 65, 255]); const edged = new Uint8ClampedArray([ 100, 100, 100, 255, 100, 100, 100, 255, 100, 100, 100, 255, 100, 100, 100, 255, 180, 180, 180, 255, 100, 100, 100, 255, 100, 100, 100, 255, 100, 100, 100, 255, 100, 100, 100, 255, ]); const sharpened = adjustBackgroundPixels(edged, 3, 3, { sharpness: 100, temperature: 0 }); expect(sharpened[16]).toBe(255); expect(sharpened[19]).toBe(255); }); it("switches background without moving overlays, resets processing, and re-extracts palette", () => { const initial = createEditorCanvasState({ assetId: "00000000-0000-4000-8000-000000000010", ratio: "3:4" }); const withOverlay = { ...initial, background: { ...initial.background, adjustments: { ...initial.background.adjustments, brightness: 42, filter: "mono" } }, elements: [element] }; const nextPalette = ["#abcdef", "#123456", "#fedcba", "#654321", "#a1b2c3"]; const switched = switchBackground(withOverlay, "00000000-0000-4000-8000-000000000011", nextPalette); expect(switched.background).toEqual({ asset_id: "00000000-0000-4000-8000-000000000011", adjustments: defaultBackgroundAdjustments() }); expect(switched.elements[0]).toMatchObject({ position: element.position, scale: element.scale, rotation: element.rotation, colors: nextPalette }); }); it("keeps preview changes immutable and allows one committed history step", () => { const initial = createEditorCanvasState({ assetId: "00000000-0000-4000-8000-000000000010", ratio: "1:1" }); const history = new CanvasEditHistory(initial); const preview = updateBackgroundAdjustments(initial, { brightness: 25, contrast: -10, fit: "fit" }); expect(initial.background.adjustments).toEqual(defaultBackgroundAdjustments()); expect(preview.background.adjustments).toMatchObject({ brightness: 25, contrast: -10, fit: "fit" }); history.commit(preview); expect(history.canUndo).toBe(true); expect(history.undo()).toEqual(initial); expect(history.canRedo).toBe(true); expect(history.redo()).toEqual(preview); }); it("strips Fabric-private fields at the persistence boundary", () => { const state = createEditorCanvasState({ assetId: "00000000-0000-4000-8000-000000000010", ratio: "9:16" }); const decoded = deserializeFabricCanvas({ ...state, _objects: [{ type: "fabric-private" }], viewportTransform: [1, 0, 0, 1, 0, 0] }); expect(decoded).toEqual(state); expect(deserializeFabricCanvas({ ...state, elements: [{ ...element, _cacheCanvas: {} }] })).toBeUndefined(); }); });