From 833cc3d086be0522ca97067f6d4206212a79b1c3 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Thu, 19 Feb 2026 08:05:33 +0500 Subject: [PATCH] fix: update persisted state workflow --- src/components/panel/Preview.svelte | 3 +- src/components/text/TextConfig.svelte | 4 +- src/lib/constants.ts | 7 +- src/states/persisted.svelte.ts | 10 +- src/states/textConfig.svelte.ts | 112 +++++++----------- src/states/texts.svelte.ts | 73 ++++++------ src/states/theme.svelte.ts | 48 ++++---- .../unit/__snapshots__/constants.test.ts.snap | 7 +- .../components/panel/PreviewManager.test.ts | 21 ++-- tests/unit/constants.test.ts | 2 +- tests/unit/services/downloadService.test.ts | 1 - tests/unit/states/persisted.svelte.test.ts | 87 ++++++++++---- tests/unit/states/textConfig.test.ts | 13 +- tests/unit/states/texts.test.ts | 26 ++-- tests/unit/states/theme.test.ts | 7 +- 15 files changed, 216 insertions(+), 205 deletions(-) diff --git a/src/components/panel/Preview.svelte b/src/components/panel/Preview.svelte index 5584ef2..a336814 100644 --- a/src/components/panel/Preview.svelte +++ b/src/components/panel/Preview.svelte @@ -31,7 +31,8 @@ {width} {height} fontSize={textConfigState.fontSize} - fill={textConfigState.color} + stroke={textConfigState.color} + fill="transparent" fontFamily={textConfigState.fontFamily} align={textConfigState.align} wrap="none" diff --git a/src/components/text/TextConfig.svelte b/src/components/text/TextConfig.svelte index 1f03d98..cd5494f 100644 --- a/src/components/text/TextConfig.svelte +++ b/src/components/text/TextConfig.svelte @@ -40,8 +40,8 @@ diff --git a/src/lib/constants.ts b/src/lib/constants.ts index be60d65..e360e6c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -35,8 +35,9 @@ export const TYPOGRAPHY = { PADDING_X_MAX: 100, // Vertical offset for range inputs - VERTICAL_OFFSET_MAX: 100, - VERTICAL_OFFSET_MIN: -100, + OFFSET_Y_MAX: 100, + OFFSET_Y_MIN: -100, + OFFSET_Y_DEFAULT: 0, // Colors TEXT_COLOR_DEFAULT: "#ffffff", @@ -82,7 +83,7 @@ export const TextAlign = { export type TextAlignType = (typeof TextAlign)[keyof typeof TextAlign]; -export const DEFAULT_TEXT_ALIGN: TextAlignType = TextAlign.CENTER; +export const TEXT_ALIGN_DEFAULT: TextAlignType = TextAlign.CENTER; export const Theme = { DARK: "dark", diff --git a/src/states/persisted.svelte.ts b/src/states/persisted.svelte.ts index 8d9a7e8..1430c56 100644 --- a/src/states/persisted.svelte.ts +++ b/src/states/persisted.svelte.ts @@ -1,14 +1,14 @@ import { browser } from "$app/environment"; -export const STATE_DATA = Symbol("state-data"); // eslint-disable-next-line @typescript-eslint/no-magic-numbers export const DEBOUNCE_DURATION = import.meta.env.MODE === "test" ? 0 : 500; export interface Persistable { - [STATE_DATA]: D; + toSnapshot(): D; + fromSnapshot(data: Partial): void; } -export function withPersistence>( +export function withPersistence>( key: string, state: T, debounceMs = DEBOUNCE_DURATION, @@ -21,7 +21,7 @@ export function withPersistence>( const parsed = JSON.parse(saved); if (parsed) { - state[STATE_DATA] = parsed; + state.fromSnapshot(parsed); } } catch (e) { console.error(`Error repairing state for ${key}`, e); @@ -30,7 +30,7 @@ export function withPersistence>( $effect.root(() => { $effect(() => { - const data = JSON.stringify($state.snapshot(state[STATE_DATA])); + const data = JSON.stringify($state.snapshot(state.toSnapshot())); if (debounceMs > 0) { const timeout = setTimeout(() => localStorage.setItem(key, data), debounceMs); diff --git a/src/states/textConfig.svelte.ts b/src/states/textConfig.svelte.ts index fb1c2b7..d9d84c6 100644 --- a/src/states/textConfig.svelte.ts +++ b/src/states/textConfig.svelte.ts @@ -1,83 +1,55 @@ -import { type TextAlignType } from "$lib/constants"; +import { TEXT_ALIGN_DEFAULT, TYPOGRAPHY, type TextAlignType } from "$lib/constants"; import type { HexColor } from "$lib/types"; -import { STATE_DATA, withPersistence } from "./persisted.svelte"; +import { withPersistence, type Persistable } from "./persisted.svelte"; -export type TextConfig = { +export type TextConfigDTO = { fontSize: number; fontFamily: string; color: HexColor; align: TextAlignType; paddingX: number; offsetY: number; + outlined: boolean; }; -function createState() { - const defaults: TextConfig = { - fontSize: 24, - fontFamily: "Arial", - color: "#ffffff", - align: "center", - paddingX: 10, - offsetY: 0, - }; - const state: TextConfig = $state({ ...defaults }); +const textConfigKeys: (keyof TextConfigDTO)[] = [ + "fontSize", + "fontFamily", + "color", + "align", + "paddingX", + "offsetY", + "outlined", +]; - return { - get fontSize() { - return state.fontSize; - }, - set fontSize(value: number) { - state.fontSize = value; - }, - get fontFamily() { - return state.fontFamily; - }, - set fontFamily(fontFamily: string) { - state.fontFamily = fontFamily; - }, - get color() { - return state.color; - }, - set color(color: HexColor) { - state.color = color; - }, - get align() { - return state.align; - }, - set align(align: TextAlignType) { - state.align = align; - }, - get paddingX() { - return state.paddingX; - }, - set paddingX(paddingX: number) { - state.paddingX = paddingX; - }, - get offsetY() { - return state.offsetY; - }, - set offsetY(offsetY: number) { - state.offsetY = offsetY; - }, - get [STATE_DATA]() { - return { - fontSize: state.fontSize, - fontFamily: state.fontFamily, - color: state.color, - align: state.align, - paddingX: state.paddingX, - offsetY: state.offsetY, - }; - }, - set [STATE_DATA](newConfig: TextConfig) { - state.fontSize = newConfig.fontSize; - state.fontFamily = newConfig.fontFamily; - state.color = newConfig.color; - state.align = newConfig.align; - state.paddingX = newConfig.paddingX; - state.offsetY = newConfig.offsetY; - }, - }; +export class TextConfigState implements Persistable { + fontSize: number = $state(TYPOGRAPHY.FONT_SIZE_DEFAULT); + fontFamily: string = $state(TYPOGRAPHY.FONT_FAMILY_DEFAULT); + color: HexColor = $state(TYPOGRAPHY.TEXT_COLOR_DEFAULT); + align: TextAlignType = $state(TEXT_ALIGN_DEFAULT); + paddingX: number = $state(TYPOGRAPHY.PADDING_X_DEFAULT); + offsetY: number = $state(TYPOGRAPHY.OFFSET_Y_DEFAULT); + outlined: boolean = $state(false); + + toSnapshot(): TextConfigDTO { + return { + fontSize: this.fontSize, + fontFamily: this.fontFamily, + color: this.color, + align: this.align, + paddingX: this.paddingX, + offsetY: this.offsetY, + outlined: this.outlined, + }; + } + + fromSnapshot(data: Partial): void { + for (const key of textConfigKeys) { + if (key in data && data[key] !== undefined) { + (this as TextConfigDTO)[key] = data[key] as never; + } + } + } } -export const textConfigState = withPersistence("text-config", createState()); +export const textConfigState = withPersistence("text-config", new TextConfigState()); diff --git a/src/states/texts.svelte.ts b/src/states/texts.svelte.ts index 143db00..45db0b2 100644 --- a/src/states/texts.svelte.ts +++ b/src/states/texts.svelte.ts @@ -1,43 +1,50 @@ -import { STATE_DATA, withPersistence } from "./persisted.svelte"; +import { withPersistence, type Persistable } from "./persisted.svelte"; export interface TextItem { text: string; id: number; } -const defaultTexts: Array = ["About me", "Links", "Projects"].map((text, idx) => ({ - text, - id: idx, -})); +export type TextState = Array; +export type TextStateDTO = Array; -export function createState() { - let texts: Array = $state(defaultTexts); - let nextId = $state(defaultTexts.length); +const defaultTexts: TextStateDTO = ["About me", "Links", "Projects"]; - return { - get texts() { - return texts; - }, - addText(text: string) { - if (text.trim().length === 0) return; - texts.push({ text, id: nextId }); - nextId++; - }, - removeText(id: number) { - texts = texts.filter((textItem) => textItem.id !== id); - }, - clear() { - texts = []; - nextId = 0; - }, - get [STATE_DATA]() { - return texts.map(({ text }) => text); - }, - set [STATE_DATA](newTexts: Array) { - texts = newTexts.map((text, idx) => ({ text, id: idx })); - nextId = newTexts.length; - }, - }; +export class TextsState implements Persistable { + #texts: Array = $state(this.fromDTO(defaultTexts)); + #nextId = $state(defaultTexts.length); + + get texts() { + return this.#texts; + } + + addText(text: string) { + if (text.trim().length === 0) return; + this.#texts.push({ text, id: this.#nextId }); + this.#nextId++; + } + + removeText(id: number) { + this.#texts = this.#texts.filter((textItem) => textItem.id !== id); + } + + clear() { + this.#texts = []; + this.#nextId = 0; + } + + toSnapshot(): TextStateDTO { + return this.#texts.map(({ text }) => text); + } + + fromSnapshot(data: TextStateDTO) { + this.#texts = this.fromDTO(data); + this.#nextId = data.length; + } + + fromDTO(data: TextStateDTO): TextState { + return data.map((text, idx) => ({ text, id: idx })); + } } -export const textsState = withPersistence("texts", createState()); +export const textsState = withPersistence("texts", new TextsState()); diff --git a/src/states/theme.svelte.ts b/src/states/theme.svelte.ts index 7f391e4..6fd51f4 100644 --- a/src/states/theme.svelte.ts +++ b/src/states/theme.svelte.ts @@ -1,28 +1,28 @@ import { Theme, type ThemeType } from "$lib/constants"; -import { STATE_DATA, withPersistence } from "./persisted.svelte"; +import { withPersistence, type Persistable } from "./persisted.svelte"; -function createState() { - let current: ThemeType = $state(Theme.LIGHT); - - return { - get current() { - return current; - }, - set current(value: ThemeType) { - current = value; - }, - toggle() { - current = current === Theme.DARK ? Theme.LIGHT : Theme.DARK; - }, - get [STATE_DATA]() { - return { - current, - }; - }, - set [STATE_DATA](data: { current: ThemeType }) { - current = data.current; - }, - }; +export interface Theme { + current: ThemeType; } -export const themeState = withPersistence("theme", createState()); +export class ThemeState implements Persistable { + current: ThemeType = $state(Theme.LIGHT); + + toggle() { + this.current = this.current === Theme.DARK ? Theme.LIGHT : Theme.DARK; + } + + toSnapshot(): Theme { + return { + current: this.current, + }; + } + + fromSnapshot(data: Partial): void { + if (data.current !== undefined) { + this.current = data.current; + } + } +} + +export const themeState = withPersistence("theme", new ThemeState()); diff --git a/tests/unit/__snapshots__/constants.test.ts.snap b/tests/unit/__snapshots__/constants.test.ts.snap index 7c6c756..43eca83 100644 --- a/tests/unit/__snapshots__/constants.test.ts.snap +++ b/tests/unit/__snapshots__/constants.test.ts.snap @@ -2,7 +2,6 @@ exports[`Application Constants Logic > Global Contract (Snapshot) > should match the previous configuration snapshot 1`] = ` { - "DEFAULT_TEXT_ALIGN": "center", "IMAGE_SETTINGS": { "BRIGHTNESS_MAX": 100, "BRIGHTNESS_MIN": 0, @@ -35,6 +34,7 @@ exports[`Application Constants Logic > Global Contract (Snapshot) > should match "NEXT": "next", "PREV": "prev", }, + "TEXT_ALIGN_DEFAULT": "center", "TRANSITION_DURATION": 0, "TYPOGRAPHY": { "FONT_FAMILIES": [ @@ -52,12 +52,13 @@ exports[`Application Constants Logic > Global Contract (Snapshot) > should match "FONT_SIZE_MAX": 72, "FONT_SIZE_MIN": 10, "MAX_TEXT_LENGTH": 100, + "OFFSET_Y_DEFAULT": 0, + "OFFSET_Y_MAX": 100, + "OFFSET_Y_MIN": -100, "PADDING_X_DEFAULT": 10, "PADDING_X_MAX": 100, "PADDING_X_MIN": 0, "TEXT_COLOR_DEFAULT": "#ffffff", - "VERTICAL_OFFSET_MAX": 100, - "VERTICAL_OFFSET_MIN": -100, }, "TextAlign": { "CENTER": "center", diff --git a/tests/unit/components/panel/PreviewManager.test.ts b/tests/unit/components/panel/PreviewManager.test.ts index 0d9ea66..1875f6d 100644 --- a/tests/unit/components/panel/PreviewManager.test.ts +++ b/tests/unit/components/panel/PreviewManager.test.ts @@ -1,7 +1,6 @@ import PreviewManager from "$components/panel/PreviewManager.svelte"; import { downloadService } from "$services/downloadService"; import { konvaStageState } from "$states/konvaStage.svelte"; -import { STATE_DATA } from "$states/persisted.svelte"; import { textsState } from "$states/texts.svelte"; import { render, screen, waitFor } from "@testing-library/svelte"; import userEvent from "@testing-library/user-event"; @@ -21,7 +20,7 @@ vi.mock("./Preview.svelte", () => ({ describe("PreviewManager Integration", () => { beforeEach(() => { vi.clearAllMocks(); - textsState[STATE_DATA] = []; + textsState.fromSnapshot([]); }); it("should show empty state by aria-label when no texts", () => { @@ -30,7 +29,7 @@ describe("PreviewManager Integration", () => { }); it("should toggle navigation buttons availability based on texts length", async () => { - textsState[STATE_DATA] = ["1", "2"]; + textsState.fromSnapshot(["1", "2"]); render(PreviewManager); const nextBtn = screen.getByRole("button", { name: /next slide/i }); @@ -42,7 +41,7 @@ describe("PreviewManager Integration", () => { it("should send correct data to downloadAll service", async () => { const user = userEvent.setup(); - textsState[STATE_DATA] = ["Apple", "Banana"]; + textsState.fromSnapshot(["Apple", "Banana"]); render(PreviewManager); await user.click(screen.getByRole("button", { name: /download all/i })); @@ -57,12 +56,11 @@ describe("PreviewManager Integration", () => { it("should call downloadPanel with current active text", async () => { const user = userEvent.setup(); - textsState[STATE_DATA] = ["First", "Second"]; + textsState.fromSnapshot(["First", "Second"]); konvaStageState.stage = { node: { id: "stage-ref" } } as unknown as Stage; render(PreviewManager); - // Переходим на второй слайд await user.click(screen.getByRole("button", { name: /next slide/i })); await user.click(screen.getByRole("button", { name: /download current/i })); @@ -70,12 +68,12 @@ describe("PreviewManager Integration", () => { }); it("should return to empty state when texts are removed", async () => { - textsState[STATE_DATA] = ["Temp"]; + textsState.fromSnapshot(["Temp"]); render(PreviewManager); expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument(); - textsState[STATE_DATA] = []; + textsState.fromSnapshot([]); await waitFor(() => { expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument(); @@ -84,18 +82,15 @@ describe("PreviewManager Integration", () => { it("should automatically correct current index on items deletion", async () => { const user = userEvent.setup(); - textsState[STATE_DATA] = ["1", "2", "3"]; + textsState.fromSnapshot(["1", "2", "3"]); render(PreviewManager); - // Уходим на последний слайд await user.click(screen.getByRole("button", { name: /next slide/i })); await user.click(screen.getByRole("button", { name: /next slide/i })); - // Удаляем элементы. Индекс должен упасть с 2 до 0 - textsState[STATE_DATA] = ["Only one left"]; + textsState.fromSnapshot(["Only one left"]); await waitFor(() => { - // Проверяем, что кнопка "Next" заблокировалась (значит мы на последнем/единственном) expect(screen.getByRole("button", { name: /next slide/i })).toBeDisabled(); expect(screen.getByRole("button", { name: /previous slide/i })).toBeDisabled(); }); diff --git a/tests/unit/constants.test.ts b/tests/unit/constants.test.ts index 33b777b..206ea55 100644 --- a/tests/unit/constants.test.ts +++ b/tests/unit/constants.test.ts @@ -17,7 +17,7 @@ describe("Application Constants Logic", () => { PANEL_SETTINGS.PANEL_HEIGHT_MAX, ); - expect(TYPOGRAPHY.VERTICAL_OFFSET_MAX).toBeGreaterThan(TYPOGRAPHY.VERTICAL_OFFSET_MIN); + expect(TYPOGRAPHY.OFFSET_Y_MAX).toBeGreaterThan(TYPOGRAPHY.OFFSET_Y_MIN); }); it("should not have duplicate values in lists", () => { diff --git a/tests/unit/services/downloadService.test.ts b/tests/unit/services/downloadService.test.ts index ff3fda0..a3e6c85 100644 --- a/tests/unit/services/downloadService.test.ts +++ b/tests/unit/services/downloadService.test.ts @@ -100,7 +100,6 @@ describe("DownloadService", () => { { filename: "test-panel-5", stage: mockKonvaStage }, ]; const result = await service.downloadAll(panels); - // const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0]; const zipInstance = vi.mocked(JSZip).mock.instances[0]; expect(result.success).toBe(true); diff --git a/tests/unit/states/persisted.svelte.test.ts b/tests/unit/states/persisted.svelte.test.ts index aeb03ab..55bbf41 100644 --- a/tests/unit/states/persisted.svelte.test.ts +++ b/tests/unit/states/persisted.svelte.test.ts @@ -1,4 +1,4 @@ -import { STATE_DATA, withPersistence } from "$states/persisted.svelte"; +import { withPersistence } from "$states/persisted.svelte"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; function createMock() { @@ -40,59 +40,83 @@ describe("persisted.svelte", () => { describe("withPersistence", () => { it("should return state unchanged when not in browser", () => { + const data = $state({ test: "value" }); const mockState = { - [STATE_DATA]: { test: "value" }, + toSnapshot: () => data, + fromSnapshot: () => {}, }; const result = withPersistence("test-key", mockState); expect(result).toBe(mockState); }); it("should restore state from localStorage when valid JSON exists", () => { - const savedData = { value: 42, name: "test" }; + interface State { + value: number; + name: string; + } + const savedData: State = { value: 42, name: "test" }; + localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData)); + const data: State = $state({ value: 0, name: "" }); const state = { - [STATE_DATA]: { value: 0, name: "" }, + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.value !== undefined) data.value = newData.value; + if (newData.name !== undefined) data.name = newData.name; + }, }; withPersistence("test-key", state); - expect(state[STATE_DATA]).toEqual(savedData); + expect(state.toSnapshot()).toEqual(savedData); expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key"); }); it("should handle corrupted JSON in localStorage gracefully", () => { localStorageMock.getItem.mockReturnValue("{ invalid json"); + const data = $state({ value: 0 }); const state = { - [STATE_DATA]: { value: 0 }, + toSnapshot: () => data, + fromSnapshot: () => {}, }; expect(() => withPersistence("test-key", state)).not.toThrow(); - expect(state[STATE_DATA]).toEqual({ value: 0 }); + expect(state.toSnapshot()).toEqual({ value: 0 }); }); it("should handle empty localStorage (no saved data)", () => { localStorageMock.getItem.mockReturnValue(null); + const data = $state({ value: 0 }); const state = { - [STATE_DATA]: { value: 0 }, + toSnapshot: () => data, + fromSnapshot: () => {}, }; withPersistence("test-key", state); - expect(state[STATE_DATA]).toEqual({ value: 0 }); + expect(state.toSnapshot()).toEqual({ value: 0 }); }); it("should save state to localStorage with debounce", async () => { vi.useFakeTimers(); + interface State { + count: number; + } + const data: State = $state({ count: 1 }); + const state = { - [STATE_DATA]: { count: 1 }, + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; + }, }; withPersistence("test-key", state, 500); - state[STATE_DATA] = { count: 2 }; + state.fromSnapshot({ count: 2 }); await Promise.resolve(); vi.advanceTimersByTime(500); @@ -108,13 +132,21 @@ describe("persisted.svelte", () => { }); it("should save state immediately when debounce is 0", async () => { + interface State { + count: number; + } + const data: State = $state({ count: 1 }); + const state = { - [STATE_DATA]: { count: 1 }, + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; + }, }; withPersistence("test-key", state, 0); - state[STATE_DATA] = { count: 2 }; + state.fromSnapshot({ count: 2 }); await Promise.resolve(); @@ -126,23 +158,24 @@ describe("persisted.svelte", () => { it("should cleanup timeout on state change during debounce", async () => { vi.useFakeTimers(); - const data = $state({ count: 1 }); + interface State { + count: number; + } + const data: State = $state({ count: 1 }); const state = { - get [STATE_DATA]() { - return data; - }, - set [STATE_DATA](v) { - data.count = v.count; + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.count !== undefined) data.count = newData.count; }, }; withPersistence("test-key", state, 500); - state[STATE_DATA] = { count: 2 }; + state.fromSnapshot({ count: 2 }); await Promise.resolve(); - state[STATE_DATA] = { count: 3 }; + state.fromSnapshot({ count: 3 }); await Promise.resolve(); vi.runOnlyPendingTimers(); @@ -158,13 +191,21 @@ describe("persisted.svelte", () => { }); it("should use DEBOUNCE_DURATION constant as default", async () => { + interface State { + test: boolean; + } + const data: State = $state({ test: true }); + const state = { - [STATE_DATA]: { test: true }, + toSnapshot: () => data, + fromSnapshot: (newData: State) => { + if (newData.test !== undefined) data.test = newData.test; + }, }; withPersistence("test-key", state); - state[STATE_DATA] = { test: false }; + state.fromSnapshot({ test: false }); await Promise.resolve(); diff --git a/tests/unit/states/textConfig.test.ts b/tests/unit/states/textConfig.test.ts index 7446272..bb52b59 100644 --- a/tests/unit/states/textConfig.test.ts +++ b/tests/unit/states/textConfig.test.ts @@ -1,6 +1,5 @@ import { TextAlign } from "$lib/constants"; import type { HexColor } from "$lib/types"; -import { STATE_DATA } from "$states/persisted.svelte"; import { textConfigState } from "$states/textConfig.svelte"; import { describe, expect, it } from "vitest"; @@ -37,9 +36,8 @@ describe("textConfig.svelte", () => { }); }); - describe("STATE_DATA", () => { + describe("persistence", () => { it("should serialize and deserialize state", () => { - // Set custom values textConfigState.fontSize = 18; textConfigState.fontFamily = "Georgia"; textConfigState.color = "#00ff00" as HexColor; @@ -47,8 +45,7 @@ describe("textConfig.svelte", () => { textConfigState.paddingX = 5; textConfigState.offsetY = 15; - // Get serialized data - const data = textConfigState[STATE_DATA]; + const data = textConfigState.toSnapshot(); expect(data).toEqual({ fontSize: 18, fontFamily: "Georgia", @@ -56,17 +53,17 @@ describe("textConfig.svelte", () => { align: TextAlign.RIGHT, paddingX: 5, offsetY: 15, + outlined: false, }); - // Restore from serialized data - textConfigState[STATE_DATA] = { + textConfigState.fromSnapshot({ fontSize: 24, fontFamily: "Arial", color: "#ffffff" as HexColor, align: TextAlign.CENTER, paddingX: 10, offsetY: 0, - }; + }); expect(textConfigState.fontSize).toBe(24); expect(textConfigState.fontFamily).toBe("Arial"); diff --git a/tests/unit/states/texts.test.ts b/tests/unit/states/texts.test.ts index 053031c..6285e56 100644 --- a/tests/unit/states/texts.test.ts +++ b/tests/unit/states/texts.test.ts @@ -1,11 +1,10 @@ -import { STATE_DATA } from "$states/persisted.svelte"; -import { createState } from "$states/texts.svelte"; +import { TextsState } from "$states/texts.svelte"; import { describe, expect, it } from "vitest"; describe("texts.svelte", () => { describe("addText", () => { it("should add new text with unique ID", () => { - const state = createState(); + const state = new TextsState(); const initialLength = state.texts.length; state.addText("Test text"); @@ -16,7 +15,7 @@ describe("texts.svelte", () => { }); it("should not add empty text", () => { - const state = createState(); + const state = new TextsState(); const initialLength = state.texts.length; state.addText(""); @@ -28,7 +27,7 @@ describe("texts.svelte", () => { describe("removeText", () => { it("should remove text by ID", () => { - const state = createState(); + const state = new TextsState(); const idToRemove = state.texts[0].id; const initialLength = state.texts.length; @@ -39,7 +38,7 @@ describe("texts.svelte", () => { }); it("should not affect other texts when removing", () => { - const state = createState(); + const state = new TextsState(); const remainingTexts = state.texts.filter((item) => item.id !== state.texts[0].id); const idToRemove = state.texts[0].id; @@ -51,7 +50,7 @@ describe("texts.svelte", () => { describe("clear", () => { it("should remove all texts", () => { - const state = createState(); + const state = new TextsState(); state.addText("Test 1"); state.addText("Test 2"); state.addText("Test 3"); @@ -62,19 +61,18 @@ describe("texts.svelte", () => { }); }); - describe("STATE_DATA", () => { + describe("persistence", () => { it("should serialize and deserialize texts", () => { - const state = createState(); + const state = new TextsState(); state.clear(); state.addText("First"); state.addText("Second"); state.addText("Third"); - const data = state[STATE_DATA]; + const data = state.toSnapshot(); expect(data).toEqual(["First", "Second", "Third"]); - // Restore from serialized data - state[STATE_DATA] = ["New 1", "New 2"]; + state.fromSnapshot(["New 1", "New 2"]); expect(state.texts).toHaveLength(2); expect(state.texts[0].text).toBe("New 1"); @@ -84,10 +82,10 @@ describe("texts.svelte", () => { }); it("should handle empty array", () => { - const state = createState(); + const state = new TextsState(); state.addText("Test"); - state[STATE_DATA] = []; + state.fromSnapshot([]); expect(state.texts).toHaveLength(0); }); diff --git a/tests/unit/states/theme.test.ts b/tests/unit/states/theme.test.ts index 21235b5..87fd7e4 100644 --- a/tests/unit/states/theme.test.ts +++ b/tests/unit/states/theme.test.ts @@ -1,5 +1,4 @@ import { Theme } from "$lib/constants"; -import { STATE_DATA } from "$states/persisted.svelte"; import { themeState } from "$states/theme.svelte"; import { describe, expect, it } from "vitest"; @@ -33,12 +32,12 @@ describe("theme.svelte", () => { }); }); - describe("STATE_DATA", () => { + describe("persistence", () => { it("should serialize and deserialize theme", () => { themeState.current = Theme.DARK; - expect(themeState[STATE_DATA]).toEqual({ current: Theme.DARK }); + expect(themeState.toSnapshot()).toEqual({ current: Theme.DARK }); - themeState[STATE_DATA] = { current: Theme.LIGHT }; + themeState.fromSnapshot({ current: Theme.LIGHT }); expect(themeState.current).toBe(Theme.LIGHT); }); });