style: add eslint and prettier, fix style errors

This commit is contained in:
2026-02-13 09:59:19 +05:00
parent 5d6159fe29
commit 387c1751c6
101 changed files with 14596 additions and 12946 deletions
+10 -10
View File
@@ -1,10 +1,10 @@
import CropInline from "$components/image/CropInline.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("CropInline.svelte", () => {
it("should render without crashing", () => {
const { container } = render(CropInline);
expect(container).toBeInTheDocument();
});
});
import CropInline from "$components/image/CropInline.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("CropInline.svelte", () => {
it("should render without crashing", () => {
const { container } = render(CropInline);
expect(container).toBeInTheDocument();
});
});
@@ -1,9 +1,9 @@
import ImageManager from "$components/image/ImageManager.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("ImageManager.svelte", () => {
it("should render without crashing", () => {
render(ImageManager);
});
});
import ImageManager from "$components/image/ImageManager.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("ImageManager.svelte", () => {
it("should render without crashing", () => {
render(ImageManager);
});
});
+25 -25
View File
@@ -1,25 +1,25 @@
import AppHeader from "$components/layout/AppHeader.svelte";
import { themeState } from "$states/theme.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it } from "vitest";
describe("AppHeader.svelte", () => {
beforeEach(() => {
themeState.current = "dark";
});
it("should toggle theme on button click", async () => {
const user = userEvent.setup();
render(AppHeader);
const toggleButton = screen.getByRole("button", { name: /toggle theme/i });
themeState.current = "dark";
await user.click(toggleButton);
expect(themeState.current).toBe("light");
await user.click(toggleButton);
expect(themeState.current).toBe("dark");
});
});
import AppHeader from "$components/layout/AppHeader.svelte";
import { themeState } from "$states/theme.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it } from "vitest";
describe("AppHeader.svelte", () => {
beforeEach(() => {
themeState.current = "dark";
});
it("should toggle theme on button click", async () => {
const user = userEvent.setup();
render(AppHeader);
const toggleButton = screen.getByRole("button", { name: /toggle theme/i });
themeState.current = "dark";
await user.click(toggleButton);
expect(themeState.current).toBe("light");
await user.click(toggleButton);
expect(themeState.current).toBe("dark");
});
});
+11 -11
View File
@@ -1,11 +1,11 @@
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import CardTest from "./CardTest.svelte";
describe("Card.svelte", () => {
it("should render without crashing", () => {
render(CardTest);
expect(screen.getByText("Test Title")).toBeInTheDocument();
expect(screen.getByTestId("card-content")).toBeInTheDocument();
});
});
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import CardTest from "./CardTest.svelte";
describe("Card.svelte", () => {
it("should render without crashing", () => {
render(CardTest);
expect(screen.getByText("Test Title")).toBeInTheDocument();
expect(screen.getByTestId("card-content")).toBeInTheDocument();
});
});
+7 -7
View File
@@ -1,7 +1,7 @@
<script>
import Card from "$components/layout/Card.svelte";
</script>
<Card title="Test Title">
<div data-testid="card-content">Test content</div>
</Card>
<script>
import Card from "$components/layout/Card.svelte";
</script>
<Card title="Test Title">
<div data-testid="card-content">Test content</div>
</Card>
+12 -12
View File
@@ -1,12 +1,12 @@
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import InputGroupTest from "./InputGroupTest.svelte";
describe("InputGroup.svelte", () => {
it("should render without crashing", () => {
render(InputGroupTest);
expect(screen.getByTestId("input1")).toBeInTheDocument();
expect(screen.getByTestId("input2")).toBeInTheDocument();
expect(screen.getByTestId("button")).toBeInTheDocument();
});
});
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import InputGroupTest from "./InputGroupTest.svelte";
describe("InputGroup.svelte", () => {
it("should render without crashing", () => {
render(InputGroupTest);
expect(screen.getByTestId("input1")).toBeInTheDocument();
expect(screen.getByTestId("input2")).toBeInTheDocument();
expect(screen.getByTestId("button")).toBeInTheDocument();
});
});
@@ -1,9 +1,9 @@
<script>
import InputGroup from "$components/layout/InputGroup.svelte";
</script>
<InputGroup>
<input type="text" data-testid="input1" />
<input type="text" data-testid="input2" />
<button data-testid="button">Кнопка</button>
</InputGroup>
<script>
import InputGroup from "$components/layout/InputGroup.svelte";
</script>
<InputGroup>
<input type="text" data-testid="input1" />
<input type="text" data-testid="input2" />
<button data-testid="button">Кнопка</button>
</InputGroup>
+10 -10
View File
@@ -1,10 +1,10 @@
import PanelBar from "$components/layout/PanelBar.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("PanelBar.svelte", () => {
it("should render without crashing", () => {
const { container } = render(PanelBar);
expect(container).toBeInTheDocument();
});
});
import PanelBar from "$components/layout/PanelBar.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("PanelBar.svelte", () => {
it("should render without crashing", () => {
const { container } = render(PanelBar);
expect(container).toBeInTheDocument();
});
});
@@ -1,12 +1,12 @@
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import SettingsGridTest from "./SettingsGridTest.svelte";
describe("SettingsGrid.svelte", () => {
it("should render without crashing", () => {
render(SettingsGridTest);
expect(screen.getByTestId("item1")).toBeInTheDocument();
expect(screen.getByTestId("item2")).toBeInTheDocument();
expect(screen.getByTestId("item3")).toBeInTheDocument();
});
});
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import SettingsGridTest from "./SettingsGridTest.svelte";
describe("SettingsGrid.svelte", () => {
it("should render without crashing", () => {
render(SettingsGridTest);
expect(screen.getByTestId("item1")).toBeInTheDocument();
expect(screen.getByTestId("item2")).toBeInTheDocument();
expect(screen.getByTestId("item3")).toBeInTheDocument();
});
});
@@ -1,9 +1,9 @@
<script>
import SettingsGrid from "$components/layout/SettingsGrid.svelte";
</script>
<SettingsGrid>
<div data-testid="item1">Item 1</div>
<div data-testid="item2">Item 2</div>
<div data-testid="item3">Item 3</div>
</SettingsGrid>
<script>
import SettingsGrid from "$components/layout/SettingsGrid.svelte";
</script>
<SettingsGrid>
<div data-testid="item1">Item 1</div>
<div data-testid="item2">Item 2</div>
<div data-testid="item3">Item 3</div>
</SettingsGrid>
@@ -1,11 +1,11 @@
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import SettingsRowTest from "./SettingsRowTest.svelte";
describe("SettingsRow.svelte", () => {
it("should render without crashing", () => {
render(SettingsRowTest);
expect(screen.getByText("Test Label")).toBeInTheDocument();
expect(screen.getByTestId("test-input")).toBeInTheDocument();
});
});
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import SettingsRowTest from "./SettingsRowTest.svelte";
describe("SettingsRow.svelte", () => {
it("should render without crashing", () => {
render(SettingsRowTest);
expect(screen.getByText("Test Label")).toBeInTheDocument();
expect(screen.getByTestId("test-input")).toBeInTheDocument();
});
});
@@ -1,7 +1,7 @@
<script>
import SettingsRow from "$components/layout/SettingsRow.svelte";
</script>
<SettingsRow label="Test Label">
<input type="text" data-testid="test-input" />
</SettingsRow>
<script>
import SettingsRow from "$components/layout/SettingsRow.svelte";
</script>
<SettingsRow label="Test Label">
<input type="text" data-testid="test-input" />
</SettingsRow>
+10 -10
View File
@@ -1,10 +1,10 @@
import TextBar from "$components/layout/TextBar.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("TextBar.svelte", () => {
it("should render without crashing", () => {
const { container } = render(TextBar);
expect(container).toBeInTheDocument();
});
});
import TextBar from "$components/layout/TextBar.svelte";
import { render } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("TextBar.svelte", () => {
it("should render without crashing", () => {
const { container } = render(TextBar);
expect(container).toBeInTheDocument();
});
});
+32 -32
View File
@@ -1,32 +1,32 @@
import Preview from "$components/panel/Preview.svelte";
import { cleanup, render } from "@testing-library/svelte";
import { afterEach, describe, expect, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("Preview.svelte", () => {
it("should render without crashing", () => {
const { container } = render(Preview, {
props: {
text: "Test text",
stage: undefined,
},
});
expect(container).toBeInTheDocument();
});
it("should have stage element", () => {
const { container } = render(Preview, {
props: {
text: "Test",
stage: undefined,
},
});
const stage = container.querySelector("canvas");
expect(stage).toBeInTheDocument();
});
});
import Preview from "$components/panel/Preview.svelte";
import { cleanup, render } from "@testing-library/svelte";
import { afterEach, describe, expect, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("Preview.svelte", () => {
it("should render without crashing", () => {
const { container } = render(Preview, {
props: {
text: "Test text",
stage: undefined,
},
});
expect(container).toBeInTheDocument();
});
it("should have stage element", () => {
const { container } = render(Preview, {
props: {
text: "Test",
stage: undefined,
},
});
const stage = container.querySelector("canvas");
expect(stage).toBeInTheDocument();
});
});
+14 -14
View File
@@ -1,14 +1,14 @@
import PreviewAll from "$components/panel/PreviewAll.svelte";
import { textsState } from "$states/texts.svelte";
import { render } from "@testing-library/svelte";
import { beforeEach, describe, it } from "vitest";
describe("PreviewAll.svelte", () => {
beforeEach(() => {
textsState.texts.length = 0;
});
it("should render without crashing", () => {
render(PreviewAll);
});
});
import PreviewAll from "$components/panel/PreviewAll.svelte";
import { textsState } from "$states/texts.svelte";
import { render } from "@testing-library/svelte";
import { beforeEach, describe, it } from "vitest";
describe("PreviewAll.svelte", () => {
beforeEach(() => {
textsState.texts.length = 0;
});
it("should render without crashing", () => {
render(PreviewAll);
});
});
@@ -1,46 +1,46 @@
import { SlideDirection } from "$lib/constants";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import PreviewControlsTest from "./PreviewControlsTest.svelte";
describe("PreviewControls logic", () => {
it("should increment current and update direction on next click", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 0, max: 3 } });
const nextBtn = screen.getByRole("button", { name: /next slide/i });
await user.click(nextBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
expect(screen.getByTestId("direction").textContent).toBe(SlideDirection.NEXT);
});
it("should decrement current on prev click", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 2, max: 3 } });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
await user.click(prevBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
});
it("should handle boundaries and disable buttons", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 0, max: 2 } });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
const nextBtn = screen.getByRole("button", { name: /next slide/i });
expect(prevBtn).toBeDisabled();
await user.click(nextBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
expect(nextBtn).toBeDisabled();
expect(prevBtn).not.toBeDisabled();
});
});
import { SlideDirection } from "$lib/constants";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import PreviewControlsTest from "./PreviewControlsTest.svelte";
describe("PreviewControls logic", () => {
it("should increment current and update direction on next click", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 0, max: 3 } });
const nextBtn = screen.getByRole("button", { name: /next slide/i });
await user.click(nextBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
expect(screen.getByTestId("direction").textContent).toBe(SlideDirection.NEXT);
});
it("should decrement current on prev click", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 2, max: 3 } });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
await user.click(prevBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
});
it("should handle boundaries and disable buttons", async () => {
const user = userEvent.setup();
render(PreviewControlsTest, { props: { current: 0, max: 2 } });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
const nextBtn = screen.getByRole("button", { name: /next slide/i });
expect(prevBtn).toBeDisabled();
await user.click(nextBtn);
expect(screen.getByTestId("current").textContent).toBe("1");
expect(nextBtn).toBeDisabled();
expect(prevBtn).not.toBeDisabled();
});
});
@@ -1,13 +1,13 @@
<script lang="ts">
import PreviewControls from "$components/panel/PreviewControls.svelte";
import { SlideDirection } from "$lib/constants";
let { current = 0, max = 5 } = $props();
let direction = $state(SlideDirection.NEXT);
</script>
<PreviewControls bind:current bind:direction {max} />
<div data-testid="current">{current}</div>
<div data-testid="direction">{direction}</div>
<script lang="ts">
import PreviewControls from "$components/panel/PreviewControls.svelte";
import { SlideDirection } from "$lib/constants";
let { current = 0, max = 5 } = $props();
let direction = $state(SlideDirection.NEXT);
</script>
<PreviewControls bind:current bind:direction {max} />
<div data-testid="current">{current}</div>
<div data-testid="direction">{direction}</div>
+103 -102
View File
@@ -1,102 +1,103 @@
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";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("$services/downloadService", () => ({
downloadService: {
downloadAll: vi.fn(),
downloadPanel: vi.fn(),
},
}));
vi.mock("./Preview.svelte", () => ({
default: { render: () => ({}) },
}));
describe("PreviewManager Integration", () => {
beforeEach(() => {
vi.clearAllMocks();
textsState[STATE_DATA] = [];
});
it("should show empty state by aria-label when no texts", () => {
render(PreviewManager);
expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument();
});
it("should toggle navigation buttons availability based on texts length", async () => {
textsState[STATE_DATA] = ["1", "2"];
render(PreviewManager);
const nextBtn = screen.getByRole("button", { name: /next slide/i });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
expect(nextBtn).not.toBeDisabled();
expect(prevBtn).toBeDisabled();
});
it("should send correct data to downloadAll service", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["Apple", "Banana"];
render(PreviewManager);
await user.click(screen.getByRole("button", { name: /download all/i }));
expect(downloadService.downloadAll).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ filename: "Apple" }),
expect.objectContaining({ filename: "Banana" }),
]),
);
});
it("should call downloadPanel with current active text", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["First", "Second"];
konvaStageState.stage = { node: { id: "stage-ref" } } as any;
render(PreviewManager);
// Переходим на второй слайд
await user.click(screen.getByRole("button", { name: /next slide/i }));
await user.click(screen.getByRole("button", { name: /download current/i }));
expect(downloadService.downloadPanel).toHaveBeenCalledWith(expect.anything(), "Second");
});
it("should return to empty state when texts are removed", async () => {
textsState[STATE_DATA] = ["Temp"];
render(PreviewManager);
expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument();
textsState[STATE_DATA] = [];
await waitFor(() => {
expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument();
});
});
it("should automatically correct current index on items deletion", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["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"];
await waitFor(() => {
// Проверяем, что кнопка "Next" заблокировалась (значит мы на последнем/единственном)
expect(screen.getByRole("button", { name: /next slide/i })).toBeDisabled();
expect(screen.getByRole("button", { name: /previous slide/i })).toBeDisabled();
});
});
});
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";
import type { Stage } from "svelte-konva";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("$services/downloadService", () => ({
downloadService: {
downloadAll: vi.fn(),
downloadPanel: vi.fn(),
},
}));
vi.mock("./Preview.svelte", () => ({
default: { render: () => ({}) },
}));
describe("PreviewManager Integration", () => {
beforeEach(() => {
vi.clearAllMocks();
textsState[STATE_DATA] = [];
});
it("should show empty state by aria-label when no texts", () => {
render(PreviewManager);
expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument();
});
it("should toggle navigation buttons availability based on texts length", async () => {
textsState[STATE_DATA] = ["1", "2"];
render(PreviewManager);
const nextBtn = screen.getByRole("button", { name: /next slide/i });
const prevBtn = screen.getByRole("button", { name: /previous slide/i });
expect(nextBtn).not.toBeDisabled();
expect(prevBtn).toBeDisabled();
});
it("should send correct data to downloadAll service", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["Apple", "Banana"];
render(PreviewManager);
await user.click(screen.getByRole("button", { name: /download all/i }));
expect(downloadService.downloadAll).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({ filename: "Apple" }),
expect.objectContaining({ filename: "Banana" }),
]),
);
});
it("should call downloadPanel with current active text", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["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 }));
expect(downloadService.downloadPanel).toHaveBeenCalledWith(expect.anything(), "Second");
});
it("should return to empty state when texts are removed", async () => {
textsState[STATE_DATA] = ["Temp"];
render(PreviewManager);
expect(screen.queryByLabelText(/Empty texts info/i)).not.toBeInTheDocument();
textsState[STATE_DATA] = [];
await waitFor(() => {
expect(screen.getByLabelText(/Empty texts info/i)).toBeInTheDocument();
});
});
it("should automatically correct current index on items deletion", async () => {
const user = userEvent.setup();
textsState[STATE_DATA] = ["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"];
await waitFor(() => {
// Проверяем, что кнопка "Next" заблокировалась (значит мы на последнем/единственном)
expect(screen.getByRole("button", { name: /next slide/i })).toBeDisabled();
expect(screen.getByRole("button", { name: /previous slide/i })).toBeDisabled();
});
});
});
+13 -13
View File
@@ -1,13 +1,13 @@
import TextConfig from "$components/text/TextConfig.svelte";
import { cleanup, render } from "@testing-library/svelte";
import { afterEach, describe, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("TextConfig.svelte", () => {
it("should render without crashing", () => {
render(TextConfig);
});
});
import TextConfig from "$components/text/TextConfig.svelte";
import { cleanup, render } from "@testing-library/svelte";
import { afterEach, describe, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("TextConfig.svelte", () => {
it("should render without crashing", () => {
render(TextConfig);
});
});
@@ -1,36 +1,36 @@
import TextInlineEdit from "$components/text/TextInlineEdit.svelte";
import { cleanup, render, screen } from "@testing-library/svelte";
import { afterEach, describe, expect, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("TextInlineEdit.svelte", () => {
it("should render with initial text", () => {
render(TextInlineEdit, {
props: {
id: 1,
text: "Test text",
ondelete: () => {},
},
});
const input = screen.getByRole("textbox");
expect(input).toBeInTheDocument();
expect(input).toHaveValue("Test text");
});
it("should render with empty text", () => {
render(TextInlineEdit, {
props: {
id: 1,
text: "",
ondelete: () => {},
},
});
const input = screen.getByRole("textbox");
expect(input).toHaveValue("");
});
});
import TextInlineEdit from "$components/text/TextInlineEdit.svelte";
import { cleanup, render, screen } from "@testing-library/svelte";
import { afterEach, describe, expect, it } from "vitest";
afterEach(() => {
cleanup();
});
describe("TextInlineEdit.svelte", () => {
it("should render with initial text", () => {
render(TextInlineEdit, {
props: {
id: 1,
text: "Test text",
ondelete: () => {},
},
});
const input = screen.getByRole("textbox");
expect(input).toBeInTheDocument();
expect(input).toHaveValue("Test text");
});
it("should render with empty text", () => {
render(TextInlineEdit, {
props: {
id: 1,
text: "",
ondelete: () => {},
},
});
const input = screen.getByRole("textbox");
expect(input).toHaveValue("");
});
});
+61 -61
View File
@@ -1,61 +1,61 @@
import TextInput from "$components/text/TextInput.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
describe("TextInput.svelte", () => {
it("should render with initial value", () => {
render(TextInput, {
props: {
text: "Test text",
onenter: vi.fn(),
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
expect(input).toBeInTheDocument();
expect(input).toHaveValue("Test text");
});
it("should update value on input", async () => {
const user = userEvent.setup();
render(TextInput, {
props: {
text: "Initial",
onenter: vi.fn(),
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
await user.clear(input);
await user.type(input, "Updated text");
expect(input).toHaveValue("Updated text");
});
it("should call onenter only on Enter key and not on others", async () => {
const user = userEvent.setup();
const onenterSpy = vi.fn();
render(TextInput, {
props: {
text: "test",
onenter: onenterSpy,
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
await user.type(input, "abc");
expect(onenterSpy).not.toHaveBeenCalled();
await user.keyboard("{Escape}");
expect(onenterSpy).not.toHaveBeenCalled();
await user.type(input, "{Enter}");
expect(onenterSpy).toHaveBeenCalledTimes(1);
});
});
import TextInput from "$components/text/TextInput.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
describe("TextInput.svelte", () => {
it("should render with initial value", () => {
render(TextInput, {
props: {
text: "Test text",
onenter: vi.fn(),
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
expect(input).toBeInTheDocument();
expect(input).toHaveValue("Test text");
});
it("should update value on input", async () => {
const user = userEvent.setup();
render(TextInput, {
props: {
text: "Initial",
onenter: vi.fn(),
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
await user.clear(input);
await user.type(input, "Updated text");
expect(input).toHaveValue("Updated text");
});
it("should call onenter only on Enter key and not on others", async () => {
const user = userEvent.setup();
const onenterSpy = vi.fn();
render(TextInput, {
props: {
text: "test",
onenter: onenterSpy,
ariaLabel: "Test input",
},
});
const input = screen.getByRole("textbox", { name: /test input/i });
await user.type(input, "abc");
expect(onenterSpy).not.toHaveBeenCalled();
await user.keyboard("{Escape}");
expect(onenterSpy).not.toHaveBeenCalled();
await user.type(input, "{Enter}");
expect(onenterSpy).toHaveBeenCalledTimes(1);
});
});
+60 -60
View File
@@ -1,60 +1,60 @@
import TextManager from "$components/text/TextManager.svelte";
import { textsState } from "$states/texts.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it } from "vitest";
describe("TextManager", () => {
beforeEach(() => {
textsState.clear();
});
it("should add text to state and clear input on button click", async () => {
const user = userEvent.setup();
render(TextManager);
const input = screen.getByRole("textbox", { name: /input new text/i });
const addButton = screen.getByRole("button", { name: /add text/i });
await user.type(input, "New Note");
await user.click(addButton);
expect(textsState.texts).toHaveLength(1);
expect(textsState.texts[0].text).toBe("New Note");
expect(input).toHaveValue("");
});
it("should add text on enter key", async () => {
const user = userEvent.setup();
render(TextManager);
const input = screen.getByRole("textbox", { name: /input new text/i });
await user.type(input, "Enter Note{Enter}");
expect(textsState.texts).toHaveLength(1);
expect(textsState.texts[0].text).toBe("Enter Note");
});
it("should display all items from state", async () => {
textsState.addText("First");
textsState.addText("Second");
render(TextManager);
const items = screen.getAllByRole("listitem");
expect(items).toHaveLength(2);
});
it("should remove text from state when delete button is clicked", async () => {
const user = userEvent.setup();
textsState.addText("To be deleted");
render(TextManager);
const deleteBtn = screen.getByRole("button", { name: /delete/i });
await user.click(deleteBtn);
expect(textsState.texts).toHaveLength(0);
});
});
import TextManager from "$components/text/TextManager.svelte";
import { textsState } from "$states/texts.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it } from "vitest";
describe("TextManager", () => {
beforeEach(() => {
textsState.clear();
});
it("should add text to state and clear input on button click", async () => {
const user = userEvent.setup();
render(TextManager);
const input = screen.getByRole("textbox", { name: /input new text/i });
const addButton = screen.getByRole("button", { name: /add text/i });
await user.type(input, "New Note");
await user.click(addButton);
expect(textsState.texts).toHaveLength(1);
expect(textsState.texts[0].text).toBe("New Note");
expect(input).toHaveValue("");
});
it("should add text on enter key", async () => {
const user = userEvent.setup();
render(TextManager);
const input = screen.getByRole("textbox", { name: /input new text/i });
await user.type(input, "Enter Note{Enter}");
expect(textsState.texts).toHaveLength(1);
expect(textsState.texts[0].text).toBe("Enter Note");
});
it("should display all items from state", async () => {
textsState.addText("First");
textsState.addText("Second");
render(TextManager);
const items = screen.getAllByRole("listitem");
expect(items).toHaveLength(2);
});
it("should remove text from state when delete button is clicked", async () => {
const user = userEvent.setup();
textsState.addText("To be deleted");
render(TextManager);
const deleteBtn = screen.getByRole("button", { name: /delete/i });
await user.click(deleteBtn);
expect(textsState.texts).toHaveLength(0);
});
});
+43 -44
View File
@@ -1,44 +1,43 @@
import { TextAlign } from "$lib/constants";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import AlignmentTest from "./AlignmentTest.svelte";
describe("Alignment.svelte", () => {
it("should update state for every button by clicking in sequence", async () => {
const user = userEvent.setup();
render(AlignmentTest);
const buttons = screen.getAllByRole("radio");
const stateDisplay = screen.getByTestId("align-value");
const allButtonsToClick = [...buttons, buttons[0]];
for (const button of allButtonsToClick) {
const label = button.getAttribute("aria-label")?.toLowerCase() || "";
await user.click(button);
const finalValue = stateDisplay.textContent?.toLowerCase() || "";
expect(label).toContain(finalValue);
}
});
it("should have initial state from props", () => {
render(AlignmentTest, { props: { align: TextAlign.RIGHT } });
expect(screen.getByTestId("align-value").textContent).toBe(TextAlign.RIGHT);
});
it("should sync with initial state and change state", async () => {
const user = userEvent.setup();
const { rerender } = render(AlignmentTest, { props: { align: TextAlign.RIGHT } });
const stateDisplay = screen.getByTestId("align-value");
expect(stateDisplay.textContent).toBe(TextAlign.RIGHT);
await rerender({ align: TextAlign.CENTER });
const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") });
expect(centerBtn).toBeChecked();
});
});
import { TextAlign } from "$lib/constants";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import AlignmentTest from "./AlignmentTest.svelte";
describe("Alignment.svelte", () => {
it("should update state for every button by clicking in sequence", async () => {
const user = userEvent.setup();
render(AlignmentTest);
const buttons = screen.getAllByRole("radio");
const stateDisplay = screen.getByTestId("align-value");
const allButtonsToClick = [...buttons, buttons[0]];
for (const button of allButtonsToClick) {
const label = button.getAttribute("aria-label")?.toLowerCase() || "";
await user.click(button);
const finalValue = stateDisplay.textContent?.toLowerCase() || "";
expect(label).toContain(finalValue);
}
});
it("should have initial state from props", () => {
render(AlignmentTest, { props: { align: TextAlign.RIGHT } });
expect(screen.getByTestId("align-value").textContent).toBe(TextAlign.RIGHT);
});
it("should sync with initial state and change state", async () => {
const { rerender } = render(AlignmentTest, { props: { align: TextAlign.RIGHT } });
const stateDisplay = screen.getByTestId("align-value");
expect(stateDisplay.textContent).toBe(TextAlign.RIGHT);
await rerender({ align: TextAlign.CENTER });
const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") });
expect(centerBtn).toBeChecked();
});
});
+11 -11
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import Alignment from "$components/ui/Alignment.svelte";
import { TextAlign } from "$lib/constants";
let { align = TextAlign.LEFT } = $props();
</script>
<Alignment bind:align />
<div data-testid="align-value">{align}</div>
<script lang="ts">
import Alignment from "$components/ui/Alignment.svelte";
import { TextAlign } from "$lib/constants";
let { align = TextAlign.LEFT } = $props();
</script>
<Alignment bind:align />
<div data-testid="align-value">{align}</div>
+43 -43
View File
@@ -1,43 +1,43 @@
import Badge from "$components/ui/Badge.svelte";
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("Badge.svelte", () => {
it("should render with string text", () => {
render(Badge, {
props: {
text: "Test Badge",
},
});
expect(screen.getByText("Test Badge")).toBeInTheDocument();
});
it("should render with number text", () => {
render(Badge, {
props: {
text: 42,
},
});
expect(screen.getByText("42")).toBeInTheDocument();
});
it("should render with empty string", () => {
render(Badge, {
props: {
text: "",
},
});
});
it("should render with zero", () => {
render(Badge, {
props: {
text: 0,
},
});
expect(screen.getByText("0")).toBeInTheDocument();
});
});
import Badge from "$components/ui/Badge.svelte";
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
describe("Badge.svelte", () => {
it("should render with string text", () => {
render(Badge, {
props: {
text: "Test Badge",
},
});
expect(screen.getByText("Test Badge")).toBeInTheDocument();
});
it("should render with number text", () => {
render(Badge, {
props: {
text: 42,
},
});
expect(screen.getByText("42")).toBeInTheDocument();
});
it("should render with empty string", () => {
render(Badge, {
props: {
text: "",
},
});
});
it("should render with zero", () => {
render(Badge, {
props: {
text: 0,
},
});
expect(screen.getByText("0")).toBeInTheDocument();
});
});
+95 -95
View File
@@ -1,95 +1,95 @@
import Button from "$components/ui/Button.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import MockIcon from "./MockIcon.svelte";
describe("Button.svelte", () => {
it("should render with icon and label", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Test Button",
ariaLabel: "Test button",
},
});
expect(screen.getByText("Test Button")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /test button/i })).toBeInTheDocument();
});
it("should render with icon only", () => {
render(Button, {
props: {
icon: MockIcon,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).toBeInTheDocument();
expect(button.textContent.trim()).toBe("");
});
it("should call onclick handler", async () => {
const onclick = vi.fn();
const user = userEvent.setup();
render(Button, {
props: {
icon: MockIcon,
label: "Click me",
onclick,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
await user.click(button);
expect(onclick).toHaveBeenCalledTimes(1);
});
it("should not throw error when clicked without onclick prop", async () => {
const user = userEvent.setup();
render(Button, {
props: {
icon: MockIcon,
label: "Click me",
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(() => user.click(button)).not.toThrow();
});
it("should be disabled when disabled prop is true", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Disabled",
disabled: true,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).toBeDisabled();
});
it("should not be disabled by default", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Enabled",
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).not.toBeDisabled();
});
});
import Button from "$components/ui/Button.svelte";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import MockIcon from "./MockIcon.svelte";
describe("Button.svelte", () => {
it("should render with icon and label", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Test Button",
ariaLabel: "Test button",
},
});
expect(screen.getByText("Test Button")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /test button/i })).toBeInTheDocument();
});
it("should render with icon only", () => {
render(Button, {
props: {
icon: MockIcon,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).toBeInTheDocument();
expect(button.textContent.trim()).toBe("");
});
it("should call onclick handler", async () => {
const onclick = vi.fn();
const user = userEvent.setup();
render(Button, {
props: {
icon: MockIcon,
label: "Click me",
onclick,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
await user.click(button);
expect(onclick).toHaveBeenCalledTimes(1);
});
it("should not throw error when clicked without onclick prop", async () => {
const user = userEvent.setup();
render(Button, {
props: {
icon: MockIcon,
label: "Click me",
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(() => user.click(button)).not.toThrow();
});
it("should be disabled when disabled prop is true", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Disabled",
disabled: true,
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).toBeDisabled();
});
it("should not be disabled by default", () => {
render(Button, {
props: {
icon: MockIcon,
label: "Enabled",
ariaLabel: "Test button",
},
});
const button = screen.getByRole("button", { name: /test button/i });
expect(button).not.toBeDisabled();
});
});
+13 -13
View File
@@ -1,13 +1,13 @@
import ColorPicker from "$components/ui/ColorPicker.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("ColorPicker.svelte", () => {
it("should render without crashing", () => {
render(ColorPicker, {
props: {
value: "#ffffff",
},
});
});
});
import ColorPicker from "$components/ui/ColorPicker.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("ColorPicker.svelte", () => {
it("should render without crashing", () => {
render(ColorPicker, {
props: {
value: "#ffffff",
},
});
});
});
+3 -3
View File
@@ -1,3 +1,3 @@
<svg data-testid="mock-icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
</svg>
<svg data-testid="mock-icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
</svg>

Before

Width:  |  Height:  |  Size: 96 B

After

Width:  |  Height:  |  Size: 93 B

+21 -21
View File
@@ -1,21 +1,21 @@
import RangeSlider from "$components/ui/RangeSlider.svelte";
import { fireEvent, render, screen } from "@testing-library/svelte";
import { describe, expect, it, vi } from "vitest";
describe("RangeSlider.svelte", () => {
it("should call onchange handler", async () => {
const onchange = vi.fn();
render(RangeSlider, {
props: {
value: 50,
onchange,
},
});
const slider = screen.getByRole("slider");
await fireEvent.change(slider, { target: { value: "75" } });
expect(onchange).toHaveBeenCalledTimes(1);
});
});
import RangeSlider from "$components/ui/RangeSlider.svelte";
import { fireEvent, render, screen } from "@testing-library/svelte";
import { describe, expect, it, vi } from "vitest";
describe("RangeSlider.svelte", () => {
it("should call onchange handler", async () => {
const onchange = vi.fn();
render(RangeSlider, {
props: {
value: 50,
onchange,
},
});
const slider = screen.getByRole("slider");
await fireEvent.change(slider, { target: { value: "75" } });
expect(onchange).toHaveBeenCalledTimes(1);
});
});
+13 -13
View File
@@ -1,13 +1,13 @@
import SelectFont from "$components/ui/SelectFont.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("SelectFont.svelte", () => {
it("should render without crashing", () => {
render(SelectFont, {
props: {
value: "Arial",
},
});
});
});
import SelectFont from "$components/ui/SelectFont.svelte";
import { render } from "@testing-library/svelte";
import { describe, it } from "vitest";
describe("SelectFont.svelte", () => {
it("should render without crashing", () => {
render(SelectFont, {
props: {
value: "Arial",
},
});
});
});
+70 -68
View File
@@ -1,68 +1,70 @@
import * as Constants from "$lib/constants";
import fs from "fs";
import path from "path";
import { describe, expect, it } from "vitest";
describe("Application Constants Logic", () => {
describe("Typography & Panel Constraints", () => {
it("should have default values within allowed boundaries", () => {
const { TYPOGRAPHY, PANEL_SETTINGS } = Constants;
expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeGreaterThanOrEqual(TYPOGRAPHY.FONT_SIZE_MIN);
expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeLessThanOrEqual(TYPOGRAPHY.FONT_SIZE_MAX);
expect(TYPOGRAPHY.FONT_FAMILIES).toContain(TYPOGRAPHY.FONT_FAMILY_DEFAULT);
expect(PANEL_SETTINGS.PANEL_HEIGHT_DEFAULT).toBeLessThanOrEqual(PANEL_SETTINGS.PANEL_HEIGHT_MAX);
expect(TYPOGRAPHY.VERTICAL_OFFSET_MAX).toBeGreaterThan(TYPOGRAPHY.VERTICAL_OFFSET_MIN);
});
it("should not have duplicate values in lists", () => {
const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants;
const uniqueFonts = new Set(TYPOGRAPHY.FONT_FAMILIES);
expect(uniqueFonts.size).toBe(TYPOGRAPHY.FONT_FAMILIES.length);
const uniqueFormats = new Set(IMAGE_SETTINGS.SUPPORTED_FORMATS);
expect(uniqueFormats.size).toBe(IMAGE_SETTINGS.SUPPORTED_FORMATS.length);
});
});
describe("Integrity & Formats", () => {
it("should have valid format patterns for colors and mime-types", () => {
const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants;
expect(TYPOGRAPHY.TEXT_COLOR_DEFAULT).toMatch(/^#[0-9a-f]{6}$/i);
IMAGE_SETTINGS.SUPPORTED_FORMATS.forEach((format) => {
expect(format).toMatch(/^image\/(jpeg|jpg|png|webp|gif)$/);
});
});
});
describe("Environment Specifics", () => {
it("should set transition duration to 0 in test mode", () => {
expect(Constants.TRANSITION_DURATION).toBe(0);
});
});
describe("Global Contract (Snapshot)", () => {
it("should match the previous configuration snapshot", () => {
expect(Constants).toMatchSnapshot();
});
});
describe("Assets Existence", () => {
it("should verify that the default background image exists", () => {
const { DEFAULT_BACKGROUND_IMAGE } = Constants.PANEL_SETTINGS;
const relativePath = DEFAULT_BACKGROUND_IMAGE.replace(/^\.\//, "");
const fullPath = path.resolve(process.cwd(), "static", relativePath);
const exists = fs.existsSync(fullPath);
expect(exists, `Image not found at: ${fullPath}`).toBe(true);
});
});
});
import * as Constants from "$lib/constants";
import fs from "fs";
import path from "path";
import { describe, expect, it } from "vitest";
describe("Application Constants Logic", () => {
describe("Typography & Panel Constraints", () => {
it("should have default values within allowed boundaries", () => {
const { TYPOGRAPHY, PANEL_SETTINGS } = Constants;
expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeGreaterThanOrEqual(TYPOGRAPHY.FONT_SIZE_MIN);
expect(TYPOGRAPHY.FONT_SIZE_DEFAULT).toBeLessThanOrEqual(TYPOGRAPHY.FONT_SIZE_MAX);
expect(TYPOGRAPHY.FONT_FAMILIES).toContain(TYPOGRAPHY.FONT_FAMILY_DEFAULT);
expect(PANEL_SETTINGS.PANEL_HEIGHT_DEFAULT).toBeLessThanOrEqual(
PANEL_SETTINGS.PANEL_HEIGHT_MAX,
);
expect(TYPOGRAPHY.VERTICAL_OFFSET_MAX).toBeGreaterThan(TYPOGRAPHY.VERTICAL_OFFSET_MIN);
});
it("should not have duplicate values in lists", () => {
const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants;
const uniqueFonts = new Set(TYPOGRAPHY.FONT_FAMILIES);
expect(uniqueFonts.size).toBe(TYPOGRAPHY.FONT_FAMILIES.length);
const uniqueFormats = new Set(IMAGE_SETTINGS.SUPPORTED_FORMATS);
expect(uniqueFormats.size).toBe(IMAGE_SETTINGS.SUPPORTED_FORMATS.length);
});
});
describe("Integrity & Formats", () => {
it("should have valid format patterns for colors and mime-types", () => {
const { TYPOGRAPHY, IMAGE_SETTINGS } = Constants;
expect(TYPOGRAPHY.TEXT_COLOR_DEFAULT).toMatch(/^#[0-9a-f]{6}$/i);
IMAGE_SETTINGS.SUPPORTED_FORMATS.forEach((format) => {
expect(format).toMatch(/^image\/(jpeg|jpg|png|webp|gif)$/);
});
});
});
describe("Environment Specifics", () => {
it("should set transition duration to 0 in test mode", () => {
expect(Constants.TRANSITION_DURATION).toBe(0);
});
});
describe("Global Contract (Snapshot)", () => {
it("should match the previous configuration snapshot", () => {
expect(Constants).toMatchSnapshot();
});
});
describe("Assets Existence", () => {
it("should verify that the default background image exists", () => {
const { DEFAULT_BACKGROUND_IMAGE } = Constants.PANEL_SETTINGS;
const relativePath = DEFAULT_BACKGROUND_IMAGE.replace(/^\.\//, "");
const fullPath = path.resolve(process.cwd(), "static", relativePath);
const exists = fs.existsSync(fullPath);
expect(exists, `Image not found at: ${fullPath}`).toBe(true);
});
});
});
+207 -207
View File
@@ -1,207 +1,207 @@
import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types";
import { describe, expect, it } from "vitest";
describe("error.types", () => {
describe("AppError", () => {
it("should create AppError with message and code", () => {
const error = new AppError("Test error message", "TEST_CODE");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Test error message");
expect(error.code).toBe("TEST_CODE");
expect(error.details).toBeUndefined();
});
it("should create AppError with message, code and details", () => {
const details = { userId: 123, action: "test" };
const error = new AppError("Test error message", "TEST_CODE", details);
expect(error.details).toEqual(details);
expect(error.message).toBe("Test error message");
expect(error.code).toBe("TEST_CODE");
});
it("should have stack trace", () => {
const error = new AppError("Test error", "TEST_CODE");
expect(error.stack).toBeDefined();
expect(typeof error.stack).toBe("string");
});
it("should be throwable and catchable", () => {
expect(() => {
throw new AppError("Test error", "TEST_CODE");
}).toThrow(AppError);
});
it("should be catchable as Error", () => {
expect(() => {
throw new AppError("Test error", "TEST_CODE");
}).toThrow(Error);
});
});
describe("ImageError", () => {
it("should create ImageError with message", () => {
const error = new ImageError("Image loading failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(ImageError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Image loading failed");
expect(error.code).toBe("IMAGE_ERROR");
});
it("should have correct error code", () => {
const error = new ImageError("Test");
expect(error.code).toBe("IMAGE_ERROR");
});
it("should be identifiable as ImageError", () => {
const error = new ImageError("Test");
expect(error instanceof ImageError).toBe(true);
});
});
describe("TextError", () => {
it("should create TextError with message", () => {
const error = new TextError("Text validation failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(TextError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Text validation failed");
expect(error.code).toBe("TEXT_ERROR");
});
it("should have correct error code", () => {
const error = new TextError("Test");
expect(error.code).toBe("TEXT_ERROR");
});
it("should be identifiable as TextError", () => {
const error = new TextError("Test");
expect(error instanceof TextError).toBe(true);
});
});
describe("CanvasError", () => {
it("should create CanvasError with message", () => {
const error = new CanvasError("Canvas rendering failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(CanvasError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Canvas rendering failed");
expect(error.code).toBe("CANVAS_ERROR");
});
it("should have correct error code", () => {
const error = new CanvasError("Test");
expect(error.code).toBe("CANVAS_ERROR");
});
it("should be identifiable as CanvasError", () => {
const error = new CanvasError("Test");
expect(error instanceof CanvasError).toBe(true);
});
});
describe("StorageError", () => {
it("should create StorageError with message", () => {
const error = new StorageError("Storage operation failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(StorageError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Storage operation failed");
expect(error.code).toBe("STORAGE_ERROR");
});
it("should have correct error code", () => {
const error = new StorageError("Test");
expect(error.code).toBe("STORAGE_ERROR");
});
it("should be identifiable as StorageError", () => {
const error = new StorageError("Test");
expect(error instanceof StorageError).toBe(true);
});
});
describe("ErrorType union", () => {
it("should accept all error types", () => {
const errors: Array<AppError | ImageError | TextError | CanvasError | StorageError> = [
new AppError("Test", "TEST"),
new ImageError("Test"),
new TextError("Test"),
new CanvasError("Test"),
new StorageError("Test"),
];
errors.forEach((error) => {
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(Error);
});
});
it("should allow type narrowing with instanceof", () => {
const errors: Array<AppError | ImageError | TextError | CanvasError | StorageError> = [
new ImageError("Test"),
new TextError("Test"),
];
const imageErrors = errors.filter((e) => e instanceof ImageError);
const textErrors = errors.filter((e) => e instanceof TextError);
expect(imageErrors).toHaveLength(1);
expect(textErrors).toHaveLength(1);
});
});
describe("Error handling patterns", () => {
it("should handle errors in try-catch blocks", () => {
let caughtError: AppError | null = null;
try {
throw new ImageError("Image failed to load");
} catch (error) {
if (error instanceof AppError) {
caughtError = error;
}
}
expect(caughtError).not.toBeNull();
expect(caughtError?.code).toBe("IMAGE_ERROR");
});
it("should preserve error details through error handling", () => {
const originalError = new AppError("Test", "TEST", { id: 123 });
let caughtError: AppError | null = null;
try {
throw originalError;
} catch (error) {
if (error instanceof AppError) {
caughtError = error;
}
}
expect(caughtError?.details).toEqual({ id: 123 });
});
});
});
import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types";
import { describe, expect, it } from "vitest";
describe("error.types", () => {
describe("AppError", () => {
it("should create AppError with message and code", () => {
const error = new AppError("Test error message", "TEST_CODE");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Test error message");
expect(error.code).toBe("TEST_CODE");
expect(error.details).toBeUndefined();
});
it("should create AppError with message, code and details", () => {
const details = { userId: 123, action: "test" };
const error = new AppError("Test error message", "TEST_CODE", details);
expect(error.details).toEqual(details);
expect(error.message).toBe("Test error message");
expect(error.code).toBe("TEST_CODE");
});
it("should have stack trace", () => {
const error = new AppError("Test error", "TEST_CODE");
expect(error.stack).toBeDefined();
expect(typeof error.stack).toBe("string");
});
it("should be throwable and catchable", () => {
expect(() => {
throw new AppError("Test error", "TEST_CODE");
}).toThrow(AppError);
});
it("should be catchable as Error", () => {
expect(() => {
throw new AppError("Test error", "TEST_CODE");
}).toThrow(Error);
});
});
describe("ImageError", () => {
it("should create ImageError with message", () => {
const error = new ImageError("Image loading failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(ImageError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Image loading failed");
expect(error.code).toBe("IMAGE_ERROR");
});
it("should have correct error code", () => {
const error = new ImageError("Test");
expect(error.code).toBe("IMAGE_ERROR");
});
it("should be identifiable as ImageError", () => {
const error = new ImageError("Test");
expect(error instanceof ImageError).toBe(true);
});
});
describe("TextError", () => {
it("should create TextError with message", () => {
const error = new TextError("Text validation failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(TextError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Text validation failed");
expect(error.code).toBe("TEXT_ERROR");
});
it("should have correct error code", () => {
const error = new TextError("Test");
expect(error.code).toBe("TEXT_ERROR");
});
it("should be identifiable as TextError", () => {
const error = new TextError("Test");
expect(error instanceof TextError).toBe(true);
});
});
describe("CanvasError", () => {
it("should create CanvasError with message", () => {
const error = new CanvasError("Canvas rendering failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(CanvasError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Canvas rendering failed");
expect(error.code).toBe("CANVAS_ERROR");
});
it("should have correct error code", () => {
const error = new CanvasError("Test");
expect(error.code).toBe("CANVAS_ERROR");
});
it("should be identifiable as CanvasError", () => {
const error = new CanvasError("Test");
expect(error instanceof CanvasError).toBe(true);
});
});
describe("StorageError", () => {
it("should create StorageError with message", () => {
const error = new StorageError("Storage operation failed");
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(StorageError);
expect(error.name).toBe("AppError");
expect(error.message).toBe("Storage operation failed");
expect(error.code).toBe("STORAGE_ERROR");
});
it("should have correct error code", () => {
const error = new StorageError("Test");
expect(error.code).toBe("STORAGE_ERROR");
});
it("should be identifiable as StorageError", () => {
const error = new StorageError("Test");
expect(error instanceof StorageError).toBe(true);
});
});
describe("ErrorType union", () => {
it("should accept all error types", () => {
const errors: Array<AppError | ImageError | TextError | CanvasError | StorageError> = [
new AppError("Test", "TEST"),
new ImageError("Test"),
new TextError("Test"),
new CanvasError("Test"),
new StorageError("Test"),
];
errors.forEach((error) => {
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(Error);
});
});
it("should allow type narrowing with instanceof", () => {
const errors: Array<AppError | ImageError | TextError | CanvasError | StorageError> = [
new ImageError("Test"),
new TextError("Test"),
];
const imageErrors = errors.filter((e) => e instanceof ImageError);
const textErrors = errors.filter((e) => e instanceof TextError);
expect(imageErrors).toHaveLength(1);
expect(textErrors).toHaveLength(1);
});
});
describe("Error handling patterns", () => {
it("should handle errors in try-catch blocks", () => {
let caughtError: AppError | null = null;
try {
throw new ImageError("Image failed to load");
} catch (error) {
if (error instanceof AppError) {
caughtError = error;
}
}
expect(caughtError).not.toBeNull();
expect(caughtError?.code).toBe("IMAGE_ERROR");
});
it("should preserve error details through error handling", () => {
const originalError = new AppError("Test", "TEST", { id: 123 });
let caughtError: AppError | null = null;
try {
throw originalError;
} catch (error) {
if (error instanceof AppError) {
caughtError = error;
}
}
expect(caughtError?.details).toEqual({ id: 123 });
});
});
});
+155 -155
View File
@@ -1,155 +1,155 @@
import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types";
import { createError, formatError, logError } from "$lib/utils/errorUtils";
import { describe, expect, it, vi } from "vitest";
describe("errorUtils", () => {
describe("formatError", () => {
it("should format AppError", () => {
const error = new AppError("Test error", "TEST_CODE");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Test error");
});
it("should format Error", () => {
const error = new Error("Test error");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Test error");
});
it("should format string error", () => {
const error = "String error message";
const result = formatError(error, "Default message");
expect(result).toBe("Default message: String error message");
});
it("should format unknown error", () => {
const error = { custom: "object" };
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Произошла неизвестная ошибка");
});
it("should use default message when not provided", () => {
const error = new Error("Test error");
const result = formatError(error);
expect(result).toBe("Произошла ошибка: Test error");
});
it("should format ImageError", () => {
const error = new ImageError("Image failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Image failed");
});
it("should format TextError", () => {
const error = new TextError("Text failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Text failed");
});
it("should format CanvasError", () => {
const error = new CanvasError("Canvas failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Canvas failed");
});
it("should format StorageError", () => {
const error = new StorageError("Storage failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Storage failed");
});
});
describe("createError", () => {
it("should create AppError with message and code", () => {
const error = createError("Test error", "TEST_CODE");
expect(error).toBeInstanceOf(AppError);
expect(error.message).toBe("Test error");
expect(error.code).toBe("TEST_CODE");
expect(error.details).toBeUndefined();
});
it("should create AppError with message, code and details", () => {
const details = { userId: 123, action: "test" };
const error = createError("Test error", "TEST_CODE", details);
expect(error.details).toEqual(details);
expect(error.message).toBe("Test error");
expect(error.code).toBe("TEST_CODE");
});
});
describe("logError", () => {
it("should log Error with stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new Error("Test error");
logError(error, "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: "Test context",
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
it("should log AppError with stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new AppError("Test error", "TEST_CODE");
logError(error, "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: "Test context",
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
it("should log string error without stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
logError("String error", "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error: "String error",
context: "Test context",
timestamp: expect.any(String),
stack: undefined,
});
consoleSpy.mockRestore();
});
it("should log error without context", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new Error("Test error");
logError(error);
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: undefined,
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
});
});
import { AppError, CanvasError, ImageError, StorageError, TextError } from "$lib/error.types";
import { createError, formatError, logError } from "$lib/utils/errorUtils";
import { describe, expect, it, vi } from "vitest";
describe("errorUtils", () => {
describe("formatError", () => {
it("should format AppError", () => {
const error = new AppError("Test error", "TEST_CODE");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Test error");
});
it("should format Error", () => {
const error = new Error("Test error");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Test error");
});
it("should format string error", () => {
const error = "String error message";
const result = formatError(error, "Default message");
expect(result).toBe("Default message: String error message");
});
it("should format unknown error", () => {
const error = { custom: "object" };
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Произошла неизвестная ошибка");
});
it("should use default message when not provided", () => {
const error = new Error("Test error");
const result = formatError(error);
expect(result).toBe("Произошла ошибка: Test error");
});
it("should format ImageError", () => {
const error = new ImageError("Image failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Image failed");
});
it("should format TextError", () => {
const error = new TextError("Text failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Text failed");
});
it("should format CanvasError", () => {
const error = new CanvasError("Canvas failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Canvas failed");
});
it("should format StorageError", () => {
const error = new StorageError("Storage failed");
const result = formatError(error, "Default message");
expect(result).toBe("Default message: Storage failed");
});
});
describe("createError", () => {
it("should create AppError with message and code", () => {
const error = createError("Test error", "TEST_CODE");
expect(error).toBeInstanceOf(AppError);
expect(error.message).toBe("Test error");
expect(error.code).toBe("TEST_CODE");
expect(error.details).toBeUndefined();
});
it("should create AppError with message, code and details", () => {
const details = { userId: 123, action: "test" };
const error = createError("Test error", "TEST_CODE", details);
expect(error.details).toEqual(details);
expect(error.message).toBe("Test error");
expect(error.code).toBe("TEST_CODE");
});
});
describe("logError", () => {
it("should log Error with stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new Error("Test error");
logError(error, "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: "Test context",
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
it("should log AppError with stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new AppError("Test error", "TEST_CODE");
logError(error, "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: "Test context",
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
it("should log string error without stack", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
logError("String error", "Test context");
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error: "String error",
context: "Test context",
timestamp: expect.any(String),
stack: undefined,
});
consoleSpy.mockRestore();
});
it("should log error without context", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const error = new Error("Test error");
logError(error);
expect(consoleSpy).toHaveBeenCalledWith("Error occurred:", {
error,
context: undefined,
timestamp: expect.any(String),
stack: expect.any(String),
});
consoleSpy.mockRestore();
});
});
});
+7 -7
View File
@@ -1,7 +1,7 @@
<script>
import Layout from "$routes/+layout.svelte";
</script>
<Layout>
<span data-testid="test-child">Hello</span>
</Layout>
<script>
import Layout from "$routes/+layout.svelte";
</script>
<Layout>
<span data-testid="test-child">Hello</span>
</Layout>
+5 -5
View File
@@ -1,5 +1,5 @@
<script>
import Page from "$routes/+page.svelte";
</script>
<Page />
<script>
import Page from "$routes/+page.svelte";
</script>
<Page />
+12 -12
View File
@@ -1,12 +1,12 @@
import { prerender, ssr } from "$routes/+layout";
import { describe, expect, it } from "vitest";
describe("+layout.ts", () => {
it("should export ssr as false", () => {
expect(ssr).toBe(false);
});
it("should export prerender as true", () => {
expect(prerender).toBe(true);
});
});
import { prerender, ssr } from "$routes/+layout";
import { describe, expect, it } from "vitest";
describe("+layout.ts", () => {
it("should export ssr as false", () => {
expect(ssr).toBe(false);
});
it("should export prerender as true", () => {
expect(prerender).toBe(true);
});
});
+39 -39
View File
@@ -1,39 +1,39 @@
import { themeState } from "$states/theme.svelte";
import { render, screen } from "@testing-library/svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
import LayoutTest from "./LayoutTest.svelte";
describe("+layout.svelte", () => {
beforeEach(() => {
themeState.current = "dark";
vi.clearAllMocks();
});
it("should apply dark theme", () => {
themeState.current = "dark";
expect(themeState.current).toBe("dark");
});
it("should apply light theme", () => {
themeState.current = "light";
expect(themeState.current).toBe("light");
});
it("should toggle theme", () => {
themeState.current = "dark";
themeState.toggle();
expect(themeState.current).toBe("light");
});
});
describe("Layout Component Coverage", () => {
it("should render children snippet and initialize props", () => {
render(LayoutTest);
expect(screen.getByTestId("test-child")).toBeInTheDocument();
expect(screen.getByRole("banner")).toBeInTheDocument();
});
});
import { themeState } from "$states/theme.svelte";
import { render, screen } from "@testing-library/svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
import LayoutTest from "./LayoutTest.svelte";
describe("+layout.svelte", () => {
beforeEach(() => {
themeState.current = "dark";
vi.clearAllMocks();
});
it("should apply dark theme", () => {
themeState.current = "dark";
expect(themeState.current).toBe("dark");
});
it("should apply light theme", () => {
themeState.current = "light";
expect(themeState.current).toBe("light");
});
it("should toggle theme", () => {
themeState.current = "dark";
themeState.toggle();
expect(themeState.current).toBe("light");
});
});
describe("Layout Component Coverage", () => {
it("should render children snippet and initialize props", () => {
render(LayoutTest);
expect(screen.getByTestId("test-child")).toBeInTheDocument();
expect(screen.getByRole("banner")).toBeInTheDocument();
});
});
+10 -10
View File
@@ -1,10 +1,10 @@
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import PageTest from "./PageTest.svelte";
describe("+page.svelte", () => {
it("should render without crashing", () => {
render(PageTest);
expect(screen.getByText("Тексты панелей")).toBeInTheDocument();
});
});
import { render, screen } from "@testing-library/svelte";
import { describe, expect, it } from "vitest";
import PageTest from "./PageTest.svelte";
describe("+page.svelte", () => {
it("should render without crashing", () => {
render(PageTest);
expect(screen.getByText("Тексты панелей")).toBeInTheDocument();
});
});
+131 -125
View File
@@ -1,125 +1,131 @@
import { DownloadService, type DownloadItem } from "$services/downloadService";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("file-saver", () => {
return {
saveAs: vi.fn(),
};
});
vi.mock("jszip", () => {
const mockZipInstance = {
file: vi.fn().mockReturnThis(),
generateAsync: vi.fn().mockResolvedValue(new Blob([])),
};
return {
default: vi.fn(function () {
return mockZipInstance;
}),
};
});
describe("DownloadService", () => {
let service: DownloadService;
let mockKonvaStage: any;
beforeEach(() => {
service = new DownloadService();
vi.clearAllMocks();
mockKonvaStage = {
toBlob: vi.fn(({ callback }) => {
callback(new Blob(["test image data"], { type: "image/png" }));
}),
};
});
describe("downloadPanel", () => {
it("should export panel successfully", async () => {
const result = await service.downloadPanel(mockKonvaStage, "test-panel-1");
const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0];
expect(result.success).toBe(true);
expect(mockKonvaStage.toBlob).toHaveBeenCalled();
expect(blobArg instanceof Blob).toBe(true);
expect(fileNameArg).toBe("test-panel-1.png");
});
it("should handle Konva stage without toBlob method", async () => {
const invalidStage = { toBlob: undefined };
const result = await service.downloadPanel(invalidStage as any, "test-panel-1.png");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Konva Stage не найден или не поддерживает toBlob");
}
});
it("should handle blob creation failure", async () => {
mockKonvaStage.toBlob = vi.fn(({ callback }) => {
callback(null);
});
const result = await service.downloadPanel(mockKonvaStage, "test-panel-1.png");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Не удалось создать изображение");
}
});
});
describe("downloadAllPanels", () => {
it("should handle successful download of multiple panels", async () => {
const panels: Array<DownloadItem> = [{ filename: "test-panel-1", 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);
expect(mockKonvaStage.toBlob).toHaveBeenCalled();
expect(blobArg instanceof Blob).toBe(true);
expect(fileNameArg).toBe("panels.zip");
expect(zipInstance.file).toHaveBeenCalledTimes(1);
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything());
});
it("should add to zip all files", async () => {
const panels: Array<DownloadItem> = [
{ filename: "test-panel-1", stage: mockKonvaStage },
{ filename: "test-panel-2", stage: mockKonvaStage },
{ filename: "test-panel-3", stage: mockKonvaStage },
{ filename: "test-panel-4", stage: mockKonvaStage },
{ 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);
expect(mockKonvaStage.toBlob).toHaveBeenCalledTimes(5);
expect(zipInstance.file).toHaveBeenCalledTimes(5);
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-2.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-3.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-4.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-5.png", expect.anything());
});
it("should handle failure", async () => {
const invalidStage = { toBlob: undefined };
const panels: Array<DownloadItem> = [{ filename: "test-panel-1", stage: invalidStage as any }];
const result = await service.downloadAll(panels);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Ошибка сохранения архива");
}
});
});
});
import { DownloadService, type DownloadItem } from "$services/downloadService";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import type { Stage } from "konva/lib/Stage";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("file-saver", () => {
return {
saveAs: vi.fn(),
};
});
vi.mock("jszip", () => {
const mockZipInstance = {
file: vi.fn().mockReturnThis(),
generateAsync: vi.fn().mockResolvedValue(new Blob([])),
};
return {
default: vi.fn(function () {
return mockZipInstance;
}),
};
});
describe("DownloadService", () => {
let service: DownloadService;
let mockKonvaStage: Stage;
beforeEach(() => {
service = new DownloadService();
vi.clearAllMocks();
mockKonvaStage = {
toBlob: vi.fn(async ({ callback }) => {
callback(new Blob(["test image data"], { type: "image/png" }));
}),
} as unknown as Stage;
});
describe("downloadPanel", () => {
it("should export panel successfully", async () => {
const result = await service.downloadPanel(mockKonvaStage, "test-panel-1");
const [blobArg, fileNameArg] = vi.mocked(saveAs).mock.calls[0];
expect(result.success).toBe(true);
expect(mockKonvaStage.toBlob).toHaveBeenCalled();
expect(blobArg instanceof Blob).toBe(true);
expect(fileNameArg).toBe("test-panel-1.png");
});
it("should handle Konva stage without toBlob method", async () => {
const invalidStage = { toBlob: undefined };
const result = await service.downloadPanel(
invalidStage as unknown as Stage,
"test-panel-1.png",
);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Konva Stage не найден или не поддерживает toBlob");
}
});
it("should handle blob creation failure", async () => {
mockKonvaStage.toBlob = vi.fn(async ({ callback }) => {
callback(null);
});
const result = await service.downloadPanel(mockKonvaStage, "test-panel-1.png");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Не удалось создать изображение");
}
});
});
describe("downloadAllPanels", () => {
it("should handle successful download of multiple panels", async () => {
const panels: Array<DownloadItem> = [{ filename: "test-panel-1", 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);
expect(mockKonvaStage.toBlob).toHaveBeenCalled();
expect(blobArg instanceof Blob).toBe(true);
expect(fileNameArg).toBe("panels.zip");
expect(zipInstance.file).toHaveBeenCalledTimes(1);
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything());
});
it("should add to zip all files", async () => {
const panels: Array<DownloadItem> = [
{ filename: "test-panel-1", stage: mockKonvaStage },
{ filename: "test-panel-2", stage: mockKonvaStage },
{ filename: "test-panel-3", stage: mockKonvaStage },
{ filename: "test-panel-4", stage: mockKonvaStage },
{ 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);
expect(mockKonvaStage.toBlob).toHaveBeenCalledTimes(5);
expect(zipInstance.file).toHaveBeenCalledTimes(5);
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-1.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-2.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-3.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-4.png", expect.anything());
expect(zipInstance.file).toHaveBeenCalledWith("test-panel-5.png", expect.anything());
});
it("should handle failure", async () => {
const invalidStage = { toBlob: undefined };
const panels: Array<DownloadItem> = [
{ filename: "test-panel-1", stage: invalidStage as unknown as Stage },
];
const result = await service.downloadAll(panels);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toContain("Ошибка сохранения архива");
}
});
});
});
+216 -192
View File
@@ -1,192 +1,216 @@
import { PANEL_SETTINGS } from "$lib/constants";
import { imageConfigState, ImageConfigState } from "$states/imageConfig.svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
let lastOnload: (() => void) | null = null;
let lastOnerror: (() => void) | null = null;
vi.stubGlobal(
"Image",
class {
_onload: (() => void) | null = null;
_onerror: (() => void) | null = null;
_src: string = "";
crossOrigin: string = "";
set src(val: string) {
this._src = val;
if (val.includes("error")) {
setTimeout(() => this._onerror?.(), 1);
} else {
setTimeout(() => this._onload?.(), 1);
}
}
get src() {
return this._src;
}
set onload(val: any) {
this._onload = val;
if (val) lastOnload = val;
}
get onload() {
return this._onload;
}
set onerror(val: any) {
this._onerror = val;
if (val) lastOnerror = val;
}
get onerror() {
return this._onerror;
}
},
);
describe("ImageConfigState", () => {
beforeEach(() => {
lastOnload = null;
lastOnerror = null;
imageConfigState.reset();
});
it("should create new instance with default values", () => {
const newState = new ImageConfigState();
expect(newState.image).toBeUndefined();
expect(newState.imageLink).toBe("");
expect(newState.imageReady).toBe(false);
expect(newState.cropLeft).toBe(0);
expect(newState.cropTop).toBe(0);
expect(newState.cropRight).toBe(0);
expect(newState.cropBottom).toBe(0);
newState.destroy();
});
it("should initialize with default background image", async () => {
await imageConfigState.uploadImageByLink(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
expect(imageConfigState.imageReady).toBe(true);
expect(imageConfigState.image).toBeDefined();
expect(imageConfigState.imageLink).toBe(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
});
it("should handle manual image upload correctly", async () => {
const testLink = "https://example.com/test.png";
const uploadPromise = imageConfigState.uploadImageByLink(testLink);
expect(imageConfigState.imageReady).toBe(false);
await uploadPromise;
expect(imageConfigState.imageReady).toBe(true);
expect(imageConfigState.imageLink).toBe(testLink);
});
it("should reset state to defaults", async () => {
await imageConfigState.uploadImageByLink("some-image.png");
imageConfigState.cropLeft = 100;
imageConfigState.reset();
expect(imageConfigState.imageReady).toBe(false);
expect(imageConfigState.imageLink).toBe("");
expect(imageConfigState.cropLeft).toBe(0);
expect(imageConfigState.image).toBeUndefined();
});
it("should handle image loading error", async () => {
await expect(imageConfigState.uploadImageByLink("error-link")).rejects.toThrow("Failed to load image");
expect(imageConfigState.imageReady).toBe(false);
});
it("should abort previous upload when new upload starts", async () => {
const upload1 = imageConfigState.uploadImageByLink("test1.jpg");
const upload2 = imageConfigState.uploadImageByLink("test2.jpg");
await expect(upload1).rejects.toThrow("Aborted");
await expect(upload2).resolves.toBeUndefined();
expect(imageConfigState.imageLink).toBe("test2.jpg");
});
it("should cleanup previous image before loading new one", async () => {
await imageConfigState.uploadImageByLink("test1.jpg");
const firstImage = imageConfigState.image;
await imageConfigState.uploadImageByLink("test2.jpg");
expect(firstImage?.onload).toBeNull();
expect(firstImage?.onerror).toBeNull();
});
it("should set crop values", () => {
imageConfigState.cropLeft = 10;
imageConfigState.cropTop = 20;
imageConfigState.cropRight = 30;
imageConfigState.cropBottom = 40;
expect(imageConfigState.cropLeft).toBe(10);
expect(imageConfigState.cropTop).toBe(20);
expect(imageConfigState.cropRight).toBe(30);
expect(imageConfigState.cropBottom).toBe(40);
});
it("should cleanup image event handlers on reset", async () => {
await imageConfigState.uploadImageByLink("test.jpg");
const img = imageConfigState.image;
imageConfigState.reset();
expect(img?.onload).toBeNull();
expect(img?.onerror).toBeNull();
});
it("should abort ongoing upload on reset", async () => {
const upload = imageConfigState.uploadImageByLink("test.jpg");
imageConfigState.reset();
await expect(upload).rejects.toThrow("Aborted");
});
it("should cleanup resources on destroy", async () => {
await imageConfigState.uploadImageByLink("test.jpg");
const img = imageConfigState.image;
imageConfigState.destroy();
expect(imageConfigState.image).toBeUndefined();
expect(img?.onload).toBeNull();
expect(img?.onerror).toBeNull();
});
it("should abort ongoing upload on destroy", async () => {
const upload = imageConfigState.uploadImageByLink("test.jpg");
imageConfigState.destroy();
await expect(upload).rejects.toThrow("Aborted");
});
it("should cover aborted onload branch", async () => {
const promise = imageConfigState.uploadImageByLink("test.png");
imageConfigState.destroy();
if (lastOnload) lastOnload();
await expect(promise).rejects.toThrow();
expect(imageConfigState.imageReady).toBe(false);
});
it("should cover aborted onerror branch", async () => {
const promise = imageConfigState.uploadImageByLink("test.png");
imageConfigState.destroy();
if (lastOnerror) lastOnerror();
await expect(promise).rejects.toThrow();
expect(imageConfigState.imageReady).toBe(false);
});
});
import { PANEL_SETTINGS } from "$lib/constants";
import { imageConfigState, ImageConfigState } from "$states/imageConfig.svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
type ImageEventHandler = ((this: HTMLImageElement, ev?: Event) => void) | null;
type ImageErrorEventHandler =
| ((
this: HTMLImageElement,
ev?: string | Event,
source?: string,
lineno?: number,
colno?: number,
error?: Error,
) => void)
| null;
let lastOnload: ImageEventHandler = null;
let lastOnerror: ImageErrorEventHandler = null;
vi.stubGlobal(
"Image",
class {
_onload: ImageEventHandler = null;
_onerror: ImageErrorEventHandler = null;
_src: string = "";
crossOrigin: string = "";
set src(val: string) {
this._src = val;
if (val.includes("error")) {
setTimeout(
() => this._onerror?.call(this as unknown as HTMLImageElement, new Event("error")),
1,
);
} else {
setTimeout(
() => this._onload?.call(this as unknown as HTMLImageElement, new Event("load")),
1,
);
}
}
get src() {
return this._src;
}
set onload(val: ImageEventHandler) {
this._onload = val;
if (val) lastOnload = val;
}
get onload() {
return this._onload;
}
set onerror(val: ImageErrorEventHandler) {
this._onerror = val;
if (val) lastOnerror = val;
}
get onerror() {
return this._onerror;
}
},
);
describe("ImageConfigState", () => {
beforeEach(() => {
lastOnload = null;
lastOnerror = null;
imageConfigState.reset();
});
it("should create new instance with default values", () => {
const newState = new ImageConfigState();
expect(newState.image).toBeUndefined();
expect(newState.imageLink).toBe("");
expect(newState.imageReady).toBe(false);
expect(newState.cropLeft).toBe(0);
expect(newState.cropTop).toBe(0);
expect(newState.cropRight).toBe(0);
expect(newState.cropBottom).toBe(0);
newState.destroy();
});
it("should initialize with default background image", async () => {
await imageConfigState.uploadImageByLink(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
expect(imageConfigState.imageReady).toBe(true);
expect(imageConfigState.image).toBeDefined();
expect(imageConfigState.imageLink).toBe(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
});
it("should handle manual image upload correctly", async () => {
const testLink = "https://example.com/test.png";
const uploadPromise = imageConfigState.uploadImageByLink(testLink);
expect(imageConfigState.imageReady).toBe(false);
await uploadPromise;
expect(imageConfigState.imageReady).toBe(true);
expect(imageConfigState.imageLink).toBe(testLink);
});
it("should reset state to defaults", async () => {
await imageConfigState.uploadImageByLink("some-image.png");
imageConfigState.cropLeft = 100;
imageConfigState.reset();
expect(imageConfigState.imageReady).toBe(false);
expect(imageConfigState.imageLink).toBe("");
expect(imageConfigState.cropLeft).toBe(0);
expect(imageConfigState.image).toBeUndefined();
});
it("should handle image loading error", async () => {
await expect(imageConfigState.uploadImageByLink("error-link")).rejects.toThrow(
"Failed to load image",
);
expect(imageConfigState.imageReady).toBe(false);
});
it("should abort previous upload when new upload starts", async () => {
const upload1 = imageConfigState.uploadImageByLink("test1.jpg");
const upload2 = imageConfigState.uploadImageByLink("test2.jpg");
await expect(upload1).rejects.toThrow("Aborted");
await expect(upload2).resolves.toBeUndefined();
expect(imageConfigState.imageLink).toBe("test2.jpg");
});
it("should cleanup previous image before loading new one", async () => {
await imageConfigState.uploadImageByLink("test1.jpg");
const firstImage = imageConfigState.image;
await imageConfigState.uploadImageByLink("test2.jpg");
expect(firstImage?.onload).toBeNull();
expect(firstImage?.onerror).toBeNull();
});
it("should set crop values", () => {
imageConfigState.cropLeft = 10;
imageConfigState.cropTop = 20;
imageConfigState.cropRight = 30;
imageConfigState.cropBottom = 40;
expect(imageConfigState.cropLeft).toBe(10);
expect(imageConfigState.cropTop).toBe(20);
expect(imageConfigState.cropRight).toBe(30);
expect(imageConfigState.cropBottom).toBe(40);
});
it("should cleanup image event handlers on reset", async () => {
await imageConfigState.uploadImageByLink("test.jpg");
const img = imageConfigState.image;
imageConfigState.reset();
expect(img?.onload).toBeNull();
expect(img?.onerror).toBeNull();
});
it("should abort ongoing upload on reset", async () => {
const upload = imageConfigState.uploadImageByLink("test.jpg");
imageConfigState.reset();
await expect(upload).rejects.toThrow("Aborted");
});
it("should cleanup resources on destroy", async () => {
await imageConfigState.uploadImageByLink("test.jpg");
const img = imageConfigState.image;
imageConfigState.destroy();
expect(imageConfigState.image).toBeUndefined();
expect(img?.onload).toBeNull();
expect(img?.onerror).toBeNull();
});
it("should abort ongoing upload on destroy", async () => {
const upload = imageConfigState.uploadImageByLink("test.jpg");
imageConfigState.destroy();
await expect(upload).rejects.toThrow("Aborted");
});
it("should cover aborted onload branch", async () => {
const promise = imageConfigState.uploadImageByLink("test.png");
imageConfigState.destroy();
if (lastOnload) {
lastOnload.call(new Image() as HTMLImageElement, new Event("load"));
}
await expect(promise).rejects.toThrow();
expect(imageConfigState.imageReady).toBe(false);
});
it("should cover aborted onerror branch", async () => {
const promise = imageConfigState.uploadImageByLink("test.png");
imageConfigState.destroy();
if (lastOnerror) {
lastOnerror.call(new Image() as HTMLImageElement, new Event("load"));
}
await expect(promise).rejects.toThrow();
expect(imageConfigState.imageReady).toBe(false);
});
});
+8 -8
View File
@@ -1,8 +1,8 @@
import { konvaAllStagesState } from "$states/konvaAllStages.svelte";
import { describe, expect, it } from "vitest";
describe("konvaAllStages.svelte", () => {
it("should import successfully", () => {
expect(konvaAllStagesState).toBeDefined();
});
});
import { konvaAllStagesState } from "$states/konvaAllStages.svelte";
import { describe, expect, it } from "vitest";
describe("konvaAllStages.svelte", () => {
it("should import successfully", () => {
expect(konvaAllStagesState).toBeDefined();
});
});
+14 -54
View File
@@ -1,54 +1,14 @@
import { konvaStageState } from "$states/konvaStage.svelte";
import { describe, expect, it } from "vitest";
describe("konvaStage.svelte", () => {
describe("initial state", () => {
it("should have undefined stage initially", () => {
expect(konvaStageState.stage).toBeUndefined();
});
it("should have stage getter", () => {
expect(konvaStageState).toHaveProperty("stage");
});
it("should have stage setter", () => {
expect(() => {
konvaStageState.stage = {} as any;
}).not.toThrow();
});
});
describe("stage getter", () => {
it("should return current stage", () => {
const mockStage = { id: "test" } as any;
konvaStageState.stage = mockStage;
expect(konvaStageState.stage).toBeDefined();
expect(konvaStageState.stage).toStrictEqual(mockStage);
});
it("should be undefined only at initialization", () => {
expect(konvaStageState.stage).toBeDefined();
});
});
describe("stage setter", () => {
it("should set stage", () => {
const mockStage = { id: "test" } as any;
konvaStageState.stage = mockStage;
expect(konvaStageState.stage).toStrictEqual(mockStage);
});
it("should allow updating stage", () => {
const firstStage = { id: "first" } as any;
const secondStage = { id: "second" } as any;
konvaStageState.stage = firstStage;
expect(konvaStageState.stage).toStrictEqual(firstStage);
konvaStageState.stage = secondStage;
expect(konvaStageState.stage).toStrictEqual(secondStage);
expect(konvaStageState.stage).not.toStrictEqual(firstStage);
});
});
});
import { konvaStageState } from "$states/konvaStage.svelte";
import type { Stage } from "svelte-konva";
import { describe, expect, it } from "vitest";
describe("konvaStageState integration", () => {
it("should work", () => {
expect(konvaStageState.stage).toBeUndefined();
const mock = { name: "stage" } as unknown as Stage;
konvaStageState.stage = mock;
expect(konvaStageState.stage).toStrictEqual(mock);
});
});
+167 -158
View File
@@ -1,158 +1,167 @@
import { STATE_DATA, withPersistence } from "$states/persisted.svelte";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value;
}),
removeItem: vi.fn((key: string) => {
delete store[key];
}),
clear: vi.fn(() => {
store = {};
}),
};
})();
Object.defineProperty(globalThis, "localStorage", {
value: localStorageMock,
});
describe("persisted.svelte", () => {
beforeEach(() => {
localStorageMock.clear();
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("withPersistence", () => {
it("should return state unchanged when not in browser", () => {
const mockState = {
[STATE_DATA]: { test: "value" },
};
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" };
localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData));
const state = {
[STATE_DATA]: { value: 0, name: "" },
};
withPersistence("test-key", state);
expect(state[STATE_DATA]).toEqual(savedData);
expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key");
});
it("should handle corrupted JSON in localStorage gracefully", () => {
localStorageMock.getItem.mockReturnValue("{ invalid json");
const state = {
[STATE_DATA]: { value: 0 },
};
expect(() => withPersistence("test-key", state)).not.toThrow();
expect(state[STATE_DATA]).toEqual({ value: 0 });
});
it("should handle empty localStorage (no saved data)", () => {
localStorageMock.getItem.mockReturnValue(null);
const state = {
[STATE_DATA]: { value: 0 },
};
withPersistence("test-key", state);
expect(state[STATE_DATA]).toEqual({ value: 0 });
});
it("should save state to localStorage with debounce", async () => {
vi.useFakeTimers();
const state = {
[STATE_DATA]: { count: 1 },
};
withPersistence("test-key", state, 500);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
vi.advanceTimersByTime(500);
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 2 }));
vi.useRealTimers();
});
it("should save state immediately when debounce is 0", async () => {
const state = {
[STATE_DATA]: { count: 1 },
};
withPersistence("test-key", state, 0);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 2 }));
});
it("should cleanup timeout on state change during debounce", async () => {
vi.useFakeTimers();
const data = $state({ count: 1 });
const state = {
get [STATE_DATA]() {
return data;
},
set [STATE_DATA](v) {
data.count = v.count;
},
};
withPersistence("test-key", state, 500);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
state[STATE_DATA] = { count: 3 };
await Promise.resolve();
vi.runOnlyPendingTimers();
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledTimes(1);
expect(localStorageMock.setItem).toHaveBeenCalledWith("test-key", JSON.stringify({ count: 3 }));
vi.useRealTimers();
});
it("should use DEBOUNCE_DURATION constant as default", async () => {
const state = {
[STATE_DATA]: { test: true },
};
withPersistence("test-key", state);
state[STATE_DATA] = { test: false };
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalled();
});
});
});
import { STATE_DATA, withPersistence } from "$states/persisted.svelte";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem: vi.fn((key: string) => store[key] || null),
setItem: vi.fn((key: string, value: string) => {
store[key] = value;
}),
removeItem: vi.fn((key: string) => {
delete store[key];
}),
clear: vi.fn(() => {
store = {};
}),
};
})();
Object.defineProperty(globalThis, "localStorage", {
value: localStorageMock,
});
describe("persisted.svelte", () => {
beforeEach(() => {
localStorageMock.clear();
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("withPersistence", () => {
it("should return state unchanged when not in browser", () => {
const mockState = {
[STATE_DATA]: { test: "value" },
};
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" };
localStorageMock.getItem.mockReturnValue(JSON.stringify(savedData));
const state = {
[STATE_DATA]: { value: 0, name: "" },
};
withPersistence("test-key", state);
expect(state[STATE_DATA]).toEqual(savedData);
expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key");
});
it("should handle corrupted JSON in localStorage gracefully", () => {
localStorageMock.getItem.mockReturnValue("{ invalid json");
const state = {
[STATE_DATA]: { value: 0 },
};
expect(() => withPersistence("test-key", state)).not.toThrow();
expect(state[STATE_DATA]).toEqual({ value: 0 });
});
it("should handle empty localStorage (no saved data)", () => {
localStorageMock.getItem.mockReturnValue(null);
const state = {
[STATE_DATA]: { value: 0 },
};
withPersistence("test-key", state);
expect(state[STATE_DATA]).toEqual({ value: 0 });
});
it("should save state to localStorage with debounce", async () => {
vi.useFakeTimers();
const state = {
[STATE_DATA]: { count: 1 },
};
withPersistence("test-key", state, 500);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
vi.advanceTimersByTime(500);
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"test-key",
JSON.stringify({ count: 2 }),
);
vi.useRealTimers();
});
it("should save state immediately when debounce is 0", async () => {
const state = {
[STATE_DATA]: { count: 1 },
};
withPersistence("test-key", state, 0);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"test-key",
JSON.stringify({ count: 2 }),
);
});
it("should cleanup timeout on state change during debounce", async () => {
vi.useFakeTimers();
const data = $state({ count: 1 });
const state = {
get [STATE_DATA]() {
return data;
},
set [STATE_DATA](v) {
data.count = v.count;
},
};
withPersistence("test-key", state, 500);
state[STATE_DATA] = { count: 2 };
await Promise.resolve();
state[STATE_DATA] = { count: 3 };
await Promise.resolve();
vi.runOnlyPendingTimers();
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalledTimes(1);
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"test-key",
JSON.stringify({ count: 3 }),
);
vi.useRealTimers();
});
it("should use DEBOUNCE_DURATION constant as default", async () => {
const state = {
[STATE_DATA]: { test: true },
};
withPersistence("test-key", state);
state[STATE_DATA] = { test: false };
await Promise.resolve();
expect(localStorageMock.setItem).toHaveBeenCalled();
});
});
});
+79 -79
View File
@@ -1,79 +1,79 @@
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";
const TEXT_ALIGN_VALUES = Object.values(TextAlign);
describe("textConfig.svelte", () => {
describe("initial state", () => {
it("should have valid initial state structure", () => {
expect(typeof textConfigState.fontSize).toBe("number");
expect(typeof textConfigState.fontFamily).toBe("string");
expect(typeof textConfigState.color).toBe("string");
expect(textConfigState.color).toMatch(/^#[0-9a-fA-F]{6}$/);
expect(TEXT_ALIGN_VALUES).toContain(textConfigState.align);
expect(typeof textConfigState.paddingX).toBe("number");
expect(typeof textConfigState.offsetY).toBe("number");
});
});
describe("property setters", () => {
it("should update all properties correctly", () => {
textConfigState.fontSize = 32;
textConfigState.fontFamily = "Roboto";
textConfigState.color = "#ff0000" as HexColor;
textConfigState.align = TextAlign.LEFT;
textConfigState.paddingX = 20;
textConfigState.offsetY = -10;
expect(textConfigState.fontSize).toBe(32);
expect(textConfigState.fontFamily).toBe("Roboto");
expect(textConfigState.color).toBe("#ff0000");
expect(textConfigState.align).toBe(TextAlign.LEFT);
expect(textConfigState.paddingX).toBe(20);
expect(textConfigState.offsetY).toBe(-10);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize state", () => {
// Set custom values
textConfigState.fontSize = 18;
textConfigState.fontFamily = "Georgia";
textConfigState.color = "#00ff00" as HexColor;
textConfigState.align = TextAlign.RIGHT;
textConfigState.paddingX = 5;
textConfigState.offsetY = 15;
// Get serialized data
const data = textConfigState[STATE_DATA];
expect(data).toEqual({
fontSize: 18,
fontFamily: "Georgia",
color: "#00ff00",
align: TextAlign.RIGHT,
paddingX: 5,
offsetY: 15,
});
// Restore from serialized data
textConfigState[STATE_DATA] = {
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");
expect(textConfigState.color).toBe("#ffffff");
expect(textConfigState.align).toBe(TextAlign.CENTER);
expect(textConfigState.paddingX).toBe(10);
expect(textConfigState.offsetY).toBe(0);
});
});
});
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";
const TEXT_ALIGN_VALUES = Object.values(TextAlign);
describe("textConfig.svelte", () => {
describe("initial state", () => {
it("should have valid initial state structure", () => {
expect(typeof textConfigState.fontSize).toBe("number");
expect(typeof textConfigState.fontFamily).toBe("string");
expect(typeof textConfigState.color).toBe("string");
expect(textConfigState.color).toMatch(/^#[0-9a-fA-F]{6}$/);
expect(TEXT_ALIGN_VALUES).toContain(textConfigState.align);
expect(typeof textConfigState.paddingX).toBe("number");
expect(typeof textConfigState.offsetY).toBe("number");
});
});
describe("property setters", () => {
it("should update all properties correctly", () => {
textConfigState.fontSize = 32;
textConfigState.fontFamily = "Roboto";
textConfigState.color = "#ff0000" as HexColor;
textConfigState.align = TextAlign.LEFT;
textConfigState.paddingX = 20;
textConfigState.offsetY = -10;
expect(textConfigState.fontSize).toBe(32);
expect(textConfigState.fontFamily).toBe("Roboto");
expect(textConfigState.color).toBe("#ff0000");
expect(textConfigState.align).toBe(TextAlign.LEFT);
expect(textConfigState.paddingX).toBe(20);
expect(textConfigState.offsetY).toBe(-10);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize state", () => {
// Set custom values
textConfigState.fontSize = 18;
textConfigState.fontFamily = "Georgia";
textConfigState.color = "#00ff00" as HexColor;
textConfigState.align = TextAlign.RIGHT;
textConfigState.paddingX = 5;
textConfigState.offsetY = 15;
// Get serialized data
const data = textConfigState[STATE_DATA];
expect(data).toEqual({
fontSize: 18,
fontFamily: "Georgia",
color: "#00ff00",
align: TextAlign.RIGHT,
paddingX: 5,
offsetY: 15,
});
// Restore from serialized data
textConfigState[STATE_DATA] = {
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");
expect(textConfigState.color).toBe("#ffffff");
expect(textConfigState.align).toBe(TextAlign.CENTER);
expect(textConfigState.paddingX).toBe(10);
expect(textConfigState.offsetY).toBe(0);
});
});
});
+95 -95
View File
@@ -1,95 +1,95 @@
import { STATE_DATA } from "$states/persisted.svelte";
import { createState } 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 initialLength = state.texts.length;
state.addText("Test text");
expect(state.texts).toHaveLength(initialLength + 1);
expect(state.texts[initialLength].text).toBe("Test text");
expect(state.texts[initialLength].id).toBeDefined();
});
it("should not add empty text", () => {
const state = createState();
const initialLength = state.texts.length;
state.addText("");
state.addText(" ");
expect(state.texts).toHaveLength(initialLength);
});
});
describe("removeText", () => {
it("should remove text by ID", () => {
const state = createState();
const idToRemove = state.texts[0].id;
const initialLength = state.texts.length;
state.removeText(idToRemove);
expect(state.texts).toHaveLength(initialLength - 1);
expect(state.texts.find((item) => item.id === idToRemove)).toBeUndefined();
});
it("should not affect other texts when removing", () => {
const state = createState();
const remainingTexts = state.texts.filter((item) => item.id !== state.texts[0].id);
const idToRemove = state.texts[0].id;
state.removeText(idToRemove);
expect(state.texts).toStrictEqual(remainingTexts);
});
});
describe("clear", () => {
it("should remove all texts", () => {
const state = createState();
state.addText("Test 1");
state.addText("Test 2");
state.addText("Test 3");
state.clear();
expect(state.texts).toHaveLength(0);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize texts", () => {
const state = createState();
state.clear();
state.addText("First");
state.addText("Second");
state.addText("Third");
const data = state[STATE_DATA];
expect(data).toEqual(["First", "Second", "Third"]);
// Restore from serialized data
state[STATE_DATA] = ["New 1", "New 2"];
expect(state.texts).toHaveLength(2);
expect(state.texts[0].text).toBe("New 1");
expect(state.texts[1].text).toBe("New 2");
expect(state.texts[0].id).toBe(0);
expect(state.texts[1].id).toBe(1);
});
it("should handle empty array", () => {
const state = createState();
state.addText("Test");
state[STATE_DATA] = [];
expect(state.texts).toHaveLength(0);
});
});
});
import { STATE_DATA } from "$states/persisted.svelte";
import { createState } 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 initialLength = state.texts.length;
state.addText("Test text");
expect(state.texts).toHaveLength(initialLength + 1);
expect(state.texts[initialLength].text).toBe("Test text");
expect(state.texts[initialLength].id).toBeDefined();
});
it("should not add empty text", () => {
const state = createState();
const initialLength = state.texts.length;
state.addText("");
state.addText(" ");
expect(state.texts).toHaveLength(initialLength);
});
});
describe("removeText", () => {
it("should remove text by ID", () => {
const state = createState();
const idToRemove = state.texts[0].id;
const initialLength = state.texts.length;
state.removeText(idToRemove);
expect(state.texts).toHaveLength(initialLength - 1);
expect(state.texts.find((item) => item.id === idToRemove)).toBeUndefined();
});
it("should not affect other texts when removing", () => {
const state = createState();
const remainingTexts = state.texts.filter((item) => item.id !== state.texts[0].id);
const idToRemove = state.texts[0].id;
state.removeText(idToRemove);
expect(state.texts).toStrictEqual(remainingTexts);
});
});
describe("clear", () => {
it("should remove all texts", () => {
const state = createState();
state.addText("Test 1");
state.addText("Test 2");
state.addText("Test 3");
state.clear();
expect(state.texts).toHaveLength(0);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize texts", () => {
const state = createState();
state.clear();
state.addText("First");
state.addText("Second");
state.addText("Third");
const data = state[STATE_DATA];
expect(data).toEqual(["First", "Second", "Third"]);
// Restore from serialized data
state[STATE_DATA] = ["New 1", "New 2"];
expect(state.texts).toHaveLength(2);
expect(state.texts[0].text).toBe("New 1");
expect(state.texts[1].text).toBe("New 2");
expect(state.texts[0].id).toBe(0);
expect(state.texts[1].id).toBe(1);
});
it("should handle empty array", () => {
const state = createState();
state.addText("Test");
state[STATE_DATA] = [];
expect(state.texts).toHaveLength(0);
});
});
});
+45 -45
View File
@@ -1,45 +1,45 @@
import { Theme } from "$lib/constants";
import { STATE_DATA } from "$states/persisted.svelte";
import { themeState } from "$states/theme.svelte";
import { describe, expect, it } from "vitest";
const THEME_VALUES = Object.values(Theme);
describe("theme.svelte", () => {
describe("initial state", () => {
it("should have valid initial theme value", () => {
expect(THEME_VALUES).toContain(themeState.current);
});
});
describe("toggle", () => {
it("should toggle between themes", () => {
themeState.current = Theme.LIGHT;
themeState.toggle();
expect(themeState.current).toBe(Theme.DARK);
themeState.toggle();
expect(themeState.current).toBe(Theme.LIGHT);
});
});
describe("current setter", () => {
it("should set theme correctly", () => {
themeState.current = Theme.DARK;
expect(themeState.current).toBe(Theme.DARK);
themeState.current = Theme.LIGHT;
expect(themeState.current).toBe(Theme.LIGHT);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize theme", () => {
themeState.current = Theme.DARK;
expect(themeState[STATE_DATA]).toEqual({ current: Theme.DARK });
themeState[STATE_DATA] = { current: Theme.LIGHT };
expect(themeState.current).toBe(Theme.LIGHT);
});
});
});
import { Theme } from "$lib/constants";
import { STATE_DATA } from "$states/persisted.svelte";
import { themeState } from "$states/theme.svelte";
import { describe, expect, it } from "vitest";
const THEME_VALUES = Object.values(Theme);
describe("theme.svelte", () => {
describe("initial state", () => {
it("should have valid initial theme value", () => {
expect(THEME_VALUES).toContain(themeState.current);
});
});
describe("toggle", () => {
it("should toggle between themes", () => {
themeState.current = Theme.LIGHT;
themeState.toggle();
expect(themeState.current).toBe(Theme.DARK);
themeState.toggle();
expect(themeState.current).toBe(Theme.LIGHT);
});
});
describe("current setter", () => {
it("should set theme correctly", () => {
themeState.current = Theme.DARK;
expect(themeState.current).toBe(Theme.DARK);
themeState.current = Theme.LIGHT;
expect(themeState.current).toBe(Theme.LIGHT);
});
});
describe("STATE_DATA", () => {
it("should serialize and deserialize theme", () => {
themeState.current = Theme.DARK;
expect(themeState[STATE_DATA]).toEqual({ current: Theme.DARK });
themeState[STATE_DATA] = { current: Theme.LIGHT };
expect(themeState.current).toBe(Theme.LIGHT);
});
});
});