diff --git a/src/components/layout/InputGroupTest.svelte b/src/components/layout/InputGroupTest.svelte
new file mode 100644
index 0000000..b6ad009
--- /dev/null
+++ b/src/components/layout/InputGroupTest.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/src/components/layout/SettingsGridTest.svelte b/src/components/layout/SettingsGridTest.svelte
new file mode 100644
index 0000000..0002f1f
--- /dev/null
+++ b/src/components/layout/SettingsGridTest.svelte
@@ -0,0 +1,9 @@
+
+
+
+Item 1
+Item 2
+Item 3
+
diff --git a/src/components/layout/SettingsRowTest.svelte b/src/components/layout/SettingsRowTest.svelte
new file mode 100644
index 0000000..9ec6e54
--- /dev/null
+++ b/src/components/layout/SettingsRowTest.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/tests/unit/components/layout/AppHeader.test.ts b/tests/unit/components/layout/AppHeader.test.ts
new file mode 100644
index 0000000..c5ddd85
--- /dev/null
+++ b/tests/unit/components/layout/AppHeader.test.ts
@@ -0,0 +1,44 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it, beforeEach } from "vitest";
+import { themeState } from "$states/theme.svelte";
+import AppHeader from "$components/layout/AppHeader.svelte";
+
+describe("AppHeader.svelte", () => {
+ beforeEach(() => {
+ themeState.theme = "dark";
+ });
+
+ it("should render header with title", () => {
+ render(AppHeader);
+
+ expect(screen.getByRole("banner")).toBeInTheDocument();
+ expect(screen.getByText("Twitch Panels")).toBeInTheDocument();
+ });
+
+ it("should render theme toggle button", () => {
+ const { container } = render(AppHeader);
+
+ const toggleButton = container.querySelector(".theme-toggle");
+ expect(toggleButton).toBeInTheDocument();
+ });
+
+ it("should toggle theme on button click", async () => {
+ const { container } = render(AppHeader);
+
+ const toggleButton = container.querySelector(".theme-toggle");
+
+ themeState.theme = "dark";
+ await fireEvent.click(toggleButton);
+ expect(themeState.theme).toBe("light");
+
+ await fireEvent.click(toggleButton);
+ expect(themeState.theme).toBe("dark");
+ });
+
+ it("should have correct aria-label on toggle button", () => {
+ const { container } = render(AppHeader);
+
+ const toggleButton = container.querySelector(".theme-toggle");
+ expect(toggleButton).toHaveAttribute("aria-label", "Toggle theme");
+ });
+});
diff --git a/tests/unit/components/layout/Card.test.ts b/tests/unit/components/layout/Card.test.ts
new file mode 100644
index 0000000..0d65cb1
--- /dev/null
+++ b/tests/unit/components/layout/Card.test.ts
@@ -0,0 +1,78 @@
+import { render, screen } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import { createRawSnippet } from "svelte";
+import Card from "$components/layout/Card.svelte";
+
+describe("Card.svelte", () => {
+ it("should render with string title", () => {
+ const childrenSnippet = createRawSnippet(() => ({
+ render: () => "
Test content
",
+ }));
+
+ const { container } = render(Card, {
+ props: {
+ title: "Test Title",
+ children: childrenSnippet,
+ },
+ });
+
+ expect(container.querySelector(".card-title")).toHaveTextContent("Test Title");
+ expect(container.querySelector(".card-body")).toHaveTextContent("Test content");
+ });
+
+ it("should render with snippet title", () => {
+ const titleSnippet = createRawSnippet(() => ({
+ render: () => "Snippet Title",
+ }));
+ const childrenSnippet = createRawSnippet(() => ({
+ render: () => "Test content
",
+ }));
+
+ const { container } = render(Card, {
+ props: {
+ title: titleSnippet,
+ children: childrenSnippet,
+ },
+ });
+
+ expect(container.querySelector(".card-title")).toHaveTextContent("Snippet Title");
+ expect(container.querySelector(".card-body")).toHaveTextContent("Test content");
+ });
+
+ it("should render with titleSnippet", () => {
+ const titleSnippet = createRawSnippet(() => ({
+ render: () => "",
+ }));
+ const childrenSnippet = createRawSnippet(() => ({
+ render: () => "Test content
",
+ }));
+
+ const { container } = render(Card, {
+ props: {
+ title: "Test Title",
+ children: childrenSnippet,
+ titleSnippet: titleSnippet,
+ },
+ });
+
+ expect(container.querySelector(".card-title")).toHaveTextContent("Test Title");
+ expect(container.querySelector(".card-body")).toHaveTextContent("Test content");
+ expect(container.querySelector(".card-snippet")).toHaveTextContent("Action");
+ });
+
+ it("should render without titleSnippet", () => {
+ const childrenSnippet = createRawSnippet(() => ({
+ render: () => "Test content
",
+ }));
+
+ const { container } = render(Card, {
+ props: {
+ title: "Test Title",
+ children: childrenSnippet,
+ },
+ });
+
+ expect(container.querySelector(".card-title")).toHaveTextContent("Test Title");
+ expect(container.querySelector(".card-body")).toHaveTextContent("Test content");
+ });
+});
diff --git a/tests/unit/components/layout/InputGroup.test.ts b/tests/unit/components/layout/InputGroup.test.ts
new file mode 100644
index 0000000..323a456
--- /dev/null
+++ b/tests/unit/components/layout/InputGroup.test.ts
@@ -0,0 +1,19 @@
+import InputGroupTest from "$components/layout/InputGroupTest.svelte";
+import { render, screen } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+
+describe("InputGroup.svelte", () => {
+ it("should render multiple children using a wrapper component", () => {
+ render(InputGroupTest);
+
+ const inputGroup = document.querySelector(".input-group");
+ expect(inputGroup).toBeInTheDocument();
+
+ expect(screen.getByTestId("input1")).toBeInTheDocument();
+ expect(screen.getByTestId("input2")).toBeInTheDocument();
+ expect(screen.getByTestId("button")).toBeInTheDocument();
+
+ const inputs = inputGroup!.querySelectorAll("input");
+ expect(inputs.length).toBe(2);
+ });
+});
diff --git a/tests/unit/components/layout/SettingsGrid.test.ts b/tests/unit/components/layout/SettingsGrid.test.ts
new file mode 100644
index 0000000..5971d5c
--- /dev/null
+++ b/tests/unit/components/layout/SettingsGrid.test.ts
@@ -0,0 +1,19 @@
+import { render, screen } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import SettingsGridTest from "$components/layout/SettingsGridTest.svelte";
+
+describe("SettingsGrid.svelte", () => {
+ it("should render multiple children using a wrapper component", () => {
+ render(SettingsGridTest);
+
+ const settingsGrid = document.querySelector(".settings-grid");
+ expect(settingsGrid).toBeInTheDocument();
+
+ expect(screen.getByTestId("item1")).toBeInTheDocument();
+ expect(screen.getByTestId("item2")).toBeInTheDocument();
+ expect(screen.getByTestId("item3")).toBeInTheDocument();
+
+ const items = settingsGrid.querySelectorAll('div[data-testid]');
+ expect(items.length).toBe(3);
+ });
+});
diff --git a/tests/unit/components/layout/SettingsRow.test.ts b/tests/unit/components/layout/SettingsRow.test.ts
new file mode 100644
index 0000000..36b2581
--- /dev/null
+++ b/tests/unit/components/layout/SettingsRow.test.ts
@@ -0,0 +1,23 @@
+import { render, screen } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import SettingsRowTest from "$components/layout/SettingsRowTest.svelte";
+
+describe("SettingsRow.svelte", () => {
+ it("should render label and children using a wrapper component", () => {
+ render(SettingsRowTest);
+
+ const settingRow = document.querySelector(".setting-row");
+ expect(settingRow).toBeInTheDocument();
+
+ expect(screen.getByText("Test Label")).toBeInTheDocument();
+ expect(screen.getByTestId("test-input")).toBeInTheDocument();
+
+ const label = settingRow.querySelector(".setting-label");
+ expect(label).toBeInTheDocument();
+ expect(label.textContent).toBe("Test Label");
+
+ const control = settingRow.querySelector(".setting-control");
+ expect(control).toBeInTheDocument();
+ expect(control.querySelector('input[data-testid="test-input"]')).toBeInTheDocument();
+ });
+});
diff --git a/tests/unit/components/text/TextInput.test.ts b/tests/unit/components/text/TextInput.test.ts
new file mode 100644
index 0000000..4f7d5e2
--- /dev/null
+++ b/tests/unit/components/text/TextInput.test.ts
@@ -0,0 +1,76 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it, vi } from "vitest";
+import TextInput from "$components/text/TextInput.svelte";
+
+describe("TextInput.svelte", () => {
+ it("should render with initial value", () => {
+ const { container } = render(TextInput, {
+ props: {
+ text: "Test text",
+ onenter: vi.fn(),
+ },
+ });
+
+ const input = container.querySelector(".text-input");
+ expect(input).toBeInTheDocument();
+ expect(input).toHaveValue("Test text");
+ });
+
+ it("should update value on input", async () => {
+ const { container } = render(TextInput, {
+ props: {
+ text: "Initial",
+ onenter: vi.fn(),
+ },
+ });
+
+ const input = container.querySelector(".text-input");
+ await fireEvent.input(input, { target: { value: "Updated text" } });
+
+ expect(input).toHaveValue("Updated text");
+ });
+
+ it("should call onenter when Enter key is pressed", async () => {
+ const onenter = vi.fn();
+ const { container } = render(TextInput, {
+ props: {
+ text: "Test",
+ onenter,
+ },
+ });
+
+ const input = container.querySelector(".text-input");
+ await fireEvent.keyDown(input, { key: "Enter" });
+
+ expect(onenter).toHaveBeenCalledTimes(1);
+ });
+
+ it("should not call onenter when other keys are pressed", async () => {
+ const onenter = vi.fn();
+ const { container } = render(TextInput, {
+ props: {
+ text: "Test",
+ onenter,
+ },
+ });
+
+ const input = container.querySelector(".text-input");
+ await fireEvent.keyDown(input, { key: "Escape" });
+ await fireEvent.keyDown(input, { key: "Tab" });
+
+ expect(onenter).not.toHaveBeenCalled();
+ });
+
+ it("should have correct placeholder", () => {
+ const { container } = render(TextInput, {
+ props: {
+ text: "",
+ onenter: vi.fn(),
+ },
+ });
+
+ const input = container.querySelector(".text-input");
+ expect(input).toBeInTheDocument();
+ expect(input).toHaveAttribute("placeholder", "Введите текст...");
+ });
+});
diff --git a/tests/unit/components/ui/Alignment.test.ts b/tests/unit/components/ui/Alignment.test.ts
new file mode 100644
index 0000000..182adb0
--- /dev/null
+++ b/tests/unit/components/ui/Alignment.test.ts
@@ -0,0 +1,68 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import Alignment from "$components/ui/Alignment.svelte";
+
+describe("Alignment.svelte", () => {
+ it("should render with default left alignment", () => {
+ const { container } = render(Alignment, {
+ props: {
+ align: "left",
+ },
+ });
+
+ const buttons = container.querySelectorAll(".align-btn");
+ expect(buttons).toHaveLength(3);
+ expect(buttons[0]).toHaveClass("active");
+ });
+
+ it("should render with center alignment", () => {
+ const { container } = render(Alignment, {
+ props: {
+ align: "center",
+ },
+ });
+
+ const buttons = container.querySelectorAll(".align-btn");
+ expect(buttons[1]).toHaveClass("active");
+ });
+
+ it("should render with right alignment", () => {
+ const { container } = render(Alignment, {
+ props: {
+ align: "right",
+ },
+ });
+
+ const buttons = container.querySelectorAll(".align-btn");
+ expect(buttons[2]).toHaveClass("active");
+ });
+
+ it("should change alignment on button click", async () => {
+ const { container } = render(Alignment, {
+ props: {
+ align: "left",
+ },
+ });
+
+ const buttons = container.querySelectorAll(".align-btn");
+
+ await fireEvent.click(buttons[1]);
+ expect(buttons[1]).toHaveClass("active");
+ expect(buttons[0]).not.toHaveClass("active");
+
+ await fireEvent.click(buttons[2]);
+ expect(buttons[2]).toHaveClass("active");
+ expect(buttons[1]).not.toHaveClass("active");
+ });
+
+ it("should render all alignment buttons", () => {
+ const { container } = render(Alignment, {
+ props: {
+ align: "left",
+ },
+ });
+
+ const buttons = container.querySelectorAll(".align-btn");
+ expect(buttons).toHaveLength(3);
+ });
+});
diff --git a/tests/unit/components/ui/Badge.test.ts b/tests/unit/components/ui/Badge.test.ts
new file mode 100644
index 0000000..a98fdc3
--- /dev/null
+++ b/tests/unit/components/ui/Badge.test.ts
@@ -0,0 +1,47 @@
+import { render, screen } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import Badge from "$components/ui/Badge.svelte";
+
+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", () => {
+ const { container } = render(Badge, {
+ props: {
+ text: "",
+ },
+ });
+
+ const badge = container.querySelector(".badge");
+ expect(badge).toBeInTheDocument();
+ expect(badge).toHaveTextContent("");
+ });
+
+ it("should render with zero", () => {
+ render(Badge, {
+ props: {
+ text: 0,
+ },
+ });
+
+ expect(screen.getByText("0")).toBeInTheDocument();
+ });
+});
diff --git a/tests/unit/components/ui/Button.test.ts b/tests/unit/components/ui/Button.test.ts
new file mode 100644
index 0000000..a7865ab
--- /dev/null
+++ b/tests/unit/components/ui/Button.test.ts
@@ -0,0 +1,88 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it, vi } from "vitest";
+import Button from "$components/ui/Button.svelte";
+import MockIcon from "./__mocks__/MockIcon.svelte";
+
+describe("Button.svelte", () => {
+
+ it("should render with icon and label", () => {
+ const { container } = render(Button, {
+ props: {
+ icon: MockIcon,
+ label: "Test Button",
+ },
+ });
+
+ expect(screen.getByText("Test Button")).toBeInTheDocument();
+ expect(container.querySelector("svg")).toBeInTheDocument();
+ });
+
+ it("should render with icon only", () => {
+ const { container } = render(Button, {
+ props: {
+ icon: MockIcon,
+ },
+ });
+
+ expect(container.querySelector("svg")).toBeInTheDocument();
+ });
+
+ it("should call onclick handler", async () => {
+ const onclick = vi.fn();
+ const { container } = render(Button, {
+ props: {
+ icon: MockIcon,
+ label: "Click me",
+ onclick,
+ },
+ });
+
+ const button = container.querySelector("button");
+ await fireEvent.click(button);
+
+ expect(onclick).toHaveBeenCalledTimes(1);
+ });
+
+ it("should be disabled when disabled prop is true", () => {
+ const { container } = render(Button, {
+ props: {
+ icon: MockIcon,
+ label: "Disabled",
+ disabled: true,
+ },
+ });
+
+ const button = container.querySelector("button");
+ expect(button).toBeDisabled();
+ });
+
+ it("should not be disabled by default", () => {
+ const { container } = render(Button, {
+ props: {
+ icon: MockIcon,
+ label: "Enabled",
+ },
+ });
+
+ const button = container.querySelector("button");
+ expect(button).not.toBeDisabled();
+ });
+
+ it("should render with different types", () => {
+ const types = ["primary", "secondary", "danger", "outline", "mini"] as const;
+
+ types.forEach(type => {
+ const { container, unmount } = render(Button, {
+ props: {
+ icon: MockIcon,
+ label: `${type} Button`,
+ type,
+ },
+ });
+
+ expect(screen.getByText(`${type} Button`)).toBeInTheDocument();
+ expect(container.querySelector("svg")).toBeInTheDocument();
+ unmount();
+ });
+ });
+});
diff --git a/tests/unit/components/ui/ColorPicker.test.ts b/tests/unit/components/ui/ColorPicker.test.ts
new file mode 100644
index 0000000..347b476
--- /dev/null
+++ b/tests/unit/components/ui/ColorPicker.test.ts
@@ -0,0 +1,40 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import ColorPicker from "$components/ui/ColorPicker.svelte";
+
+describe("ColorPicker.svelte", () => {
+ it("should render with initial value", () => {
+ const { container } = render(ColorPicker, {
+ props: {
+ value: "#ffffff",
+ },
+ });
+
+ const input = container.querySelector(".color-input");
+ expect(input).toBeInTheDocument();
+ expect(screen.getByText("#ffffff")).toBeInTheDocument();
+ });
+
+ it("should update value on change", async () => {
+ const { container } = render(ColorPicker, {
+ props: {
+ value: "#ffffff",
+ },
+ });
+
+ const input = container.querySelector(".color-input");
+ await fireEvent.input(input, { target: { value: "#ff0000" } });
+
+ expect(screen.getByText("#ff0000")).toBeInTheDocument();
+ });
+
+ it("should render with different color values", () => {
+ render(ColorPicker, {
+ props: {
+ value: "#000000",
+ },
+ });
+
+ expect(screen.getByText("#000000")).toBeInTheDocument();
+ });
+});
diff --git a/tests/unit/components/ui/RangeSlider.test.ts b/tests/unit/components/ui/RangeSlider.test.ts
new file mode 100644
index 0000000..5ef784d
--- /dev/null
+++ b/tests/unit/components/ui/RangeSlider.test.ts
@@ -0,0 +1,61 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it, vi } from "vitest";
+import RangeSlider from "$components/ui/RangeSlider.svelte";
+
+describe("RangeSlider.svelte", () => {
+ it("should render with default props", () => {
+ const { container } = render(RangeSlider, {
+ props: {
+ value: 50,
+ },
+ });
+
+ const slider = container.querySelector(".slider");
+ expect(slider).toBeInTheDocument();
+ expect(screen.getByText("50")).toBeInTheDocument();
+ });
+
+ it("should render with custom min, max, step", () => {
+ const { container } = render(RangeSlider, {
+ props: {
+ value: 25,
+ min: 0,
+ max: 50,
+ step: 5,
+ },
+ });
+
+ const slider = container.querySelector(".slider");
+ expect(slider).toHaveAttribute("min", "0");
+ expect(slider).toHaveAttribute("max", "50");
+ expect(slider).toHaveAttribute("step", "5");
+ });
+
+ it("should update value on change", async () => {
+ const { container } = render(RangeSlider, {
+ props: {
+ value: 50,
+ },
+ });
+
+ const slider = container.querySelector(".slider");
+ await fireEvent.input(slider, { target: { value: "75" } });
+
+ expect(screen.getByText("75")).toBeInTheDocument();
+ });
+
+ it("should call onchange handler", async () => {
+ const onchange = vi.fn();
+ const { container } = render(RangeSlider, {
+ props: {
+ value: 50,
+ onchange,
+ },
+ });
+
+ const slider = container.querySelector(".slider");
+ await fireEvent.change(slider, { target: { value: "75" } });
+
+ expect(onchange).toHaveBeenCalled();
+ });
+});
diff --git a/tests/unit/components/ui/SelectFont.test.ts b/tests/unit/components/ui/SelectFont.test.ts
new file mode 100644
index 0000000..5a47fde
--- /dev/null
+++ b/tests/unit/components/ui/SelectFont.test.ts
@@ -0,0 +1,70 @@
+import { render, screen, fireEvent } from "@testing-library/svelte";
+import { describe, expect, it } from "vitest";
+import SelectFont from "$components/ui/SelectFont.svelte";
+
+describe("SelectFont.svelte", () => {
+ const fonts = [
+ "Arial",
+ "Verdana",
+ "Georgia",
+ "Times New Roman",
+ "Courier New",
+ "Impact",
+ "Comic Sans MS",
+ "Trebuchet MS",
+ ];
+
+ it("should render with initial value", () => {
+ const { container } = render(SelectFont, {
+ props: {
+ value: "Arial",
+ },
+ });
+
+ const select = container.querySelector(".select-input");
+ expect(select).toBeInTheDocument();
+ expect(select).toHaveValue("Arial");
+ });
+
+ it("should render all font options", () => {
+ render(SelectFont, {
+ props: {
+ value: "Arial",
+ },
+ });
+
+ fonts.forEach(font => {
+ const options = screen.queryAllByText(font);
+ expect(options.length).toBeGreaterThan(0);
+ });
+ });
+
+ it("should update value on change", async () => {
+ const { container } = render(SelectFont, {
+ props: {
+ value: "Arial",
+ },
+ });
+
+ const select = container.querySelector(".select-input");
+ await fireEvent.change(select, { target: { value: "Verdana" } });
+
+ expect(select).toHaveValue("Verdana");
+ });
+
+ it("should select different fonts", async () => {
+ const { container } = render(SelectFont, {
+ props: {
+ value: "Arial",
+ },
+ });
+
+ const select = container.querySelector(".select-input");
+
+ await fireEvent.change(select, { target: { value: "Georgia" } });
+ expect(select).toHaveValue("Georgia");
+
+ await fireEvent.change(select, { target: { value: "Impact" } });
+ expect(select).toHaveValue("Impact");
+ });
+});
diff --git a/tests/unit/components/ui/__mocks__/MockIcon.svelte b/tests/unit/components/ui/__mocks__/MockIcon.svelte
new file mode 100644
index 0000000..2785c87
--- /dev/null
+++ b/tests/unit/components/ui/__mocks__/MockIcon.svelte
@@ -0,0 +1,3 @@
+