fix: update persisted state workflow
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user