import { describe, expect, it } from "vitest"; import { toolcraftReducer } from "../composition/public-state"; import { createState } from "./reducer-test-utils"; describe("canvas aspect ratio editing", () => { it.each([ ["width", 1280, 1280, 720], ["height", 720, 1280, 720], ["width", 1000, 1000, 563], ] as const)("preserves 16:9 when committing %s %s and restores both sides through history", (dimension, value, width, height) => { const preset = toolcraftReducer(createState(), { type: "controls.setValue", target: "canvas.aspectRatio", value: "16:9", }); const changed = toolcraftReducer(preset, { type: "controls.setValue", target: `canvas.size.${dimension}`, value, }); expect(changed.canvas.size).toEqual({ width, height, unit: "px" }); expect(changed.values["canvas.aspectRatio"]).toEqual(preset.values["canvas.aspectRatio"]); expect(changed.values["canvas.size.width"]).toBe(width); expect(changed.values["canvas.size.height"]).toBe(height); expect(changed.history.undo).toHaveLength(preset.history.undo.length + 1); const undone = toolcraftReducer(changed, { type: "history.undo" }); expect(undone.canvas.size).toEqual(preset.canvas.size); expect(undone.values["canvas.aspectRatio"]).toEqual(preset.values["canvas.aspectRatio"]); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(redone.canvas.size).toEqual(changed.canvas.size); expect(redone.values["canvas.aspectRatio"]).toEqual(changed.values["canvas.aspectRatio"]); }); it("keeps a custom ratio when editing either pixel dimension", () => { const custom = toolcraftReducer(createState(), { type: "controls.setValue", target: "canvas.aspectRatio", value: { mode: "custom", width: 5, height: 4, value: "5:4" }, }); const width = toolcraftReducer(custom, { type: "controls.setValue", target: "canvas.size.width", value: 1000, }); expect(width.canvas.size).toEqual({ width: 1000, height: 800, unit: "px" }); const height = toolcraftReducer(width, { type: "controls.setValue", target: "canvas.size.height", value: 400, }); expect(height.canvas.size).toEqual({ width: 500, height: 400, unit: "px" }); expect(height.values["canvas.aspectRatio"]).toEqual(custom.values["canvas.aspectRatio"]); }); it("does not round-trip rounded pixels on repeated commits or entering Custom", () => { const preset = toolcraftReducer(createState(), { type: "controls.setValue", target: "canvas.aspectRatio", value: "16:9", }); const rounded = toolcraftReducer(preset, { type: "controls.setValue", target: "canvas.size.width", value: 1000, }); const repeated = toolcraftReducer(rounded, { type: "controls.setValue", target: "canvas.size.height", value: 563, }); expect(repeated).toBe(rounded); const custom = toolcraftReducer(rounded, { type: "controls.setValue", target: "canvas.aspectRatio", value: { mode: "custom", width: 16, height: 9, value: "16:9" }, }); expect(custom.canvas.size).toEqual(rounded.canvas.size); expect(custom.values["canvas.aspectRatio"]).toMatchObject({ mode: "custom" }); }); it("ignores invalid dimensions and keeps finite sizing dormant in Infinity", () => { const state = createState(); for (const value of [NaN, Infinity, "invalid"]) { expect(toolcraftReducer(state, { type: "controls.setValue", target: "canvas.size.width", value, })).toBe(state); } const infinite = toolcraftReducer(state, { type: "controls.setValue", target: "canvas.infinity", value: true, }); for (const [target, value] of [["canvas.size.width", 200], ["canvas.aspectRatio", "16:9"]] as const) { expect(toolcraftReducer(infinite, { type: "controls.setValue", target, value })).toBe(infinite); } }); });