From 1a0d99828ef1ef4519e1341a96e661aa6c653a9c Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sun, 8 Feb 2026 04:14:03 +0500 Subject: [PATCH] test: add test for svelte components --- src/components/layout/InputGroupTest.svelte | 9 ++ src/components/layout/SettingsGridTest.svelte | 9 ++ src/components/layout/SettingsRowTest.svelte | 7 ++ .../unit/components/layout/AppHeader.test.ts | 44 ++++++++++ tests/unit/components/layout/Card.test.ts | 78 ++++++++++++++++ .../unit/components/layout/InputGroup.test.ts | 19 ++++ .../components/layout/SettingsGrid.test.ts | 19 ++++ .../components/layout/SettingsRow.test.ts | 23 +++++ tests/unit/components/text/TextInput.test.ts | 76 ++++++++++++++++ tests/unit/components/ui/Alignment.test.ts | 68 ++++++++++++++ tests/unit/components/ui/Badge.test.ts | 47 ++++++++++ tests/unit/components/ui/Button.test.ts | 88 +++++++++++++++++++ tests/unit/components/ui/ColorPicker.test.ts | 40 +++++++++ tests/unit/components/ui/RangeSlider.test.ts | 61 +++++++++++++ tests/unit/components/ui/SelectFont.test.ts | 70 +++++++++++++++ .../components/ui/__mocks__/MockIcon.svelte | 3 + 16 files changed, 661 insertions(+) create mode 100644 src/components/layout/InputGroupTest.svelte create mode 100644 src/components/layout/SettingsGridTest.svelte create mode 100644 src/components/layout/SettingsRowTest.svelte create mode 100644 tests/unit/components/layout/AppHeader.test.ts create mode 100644 tests/unit/components/layout/Card.test.ts create mode 100644 tests/unit/components/layout/InputGroup.test.ts create mode 100644 tests/unit/components/layout/SettingsGrid.test.ts create mode 100644 tests/unit/components/layout/SettingsRow.test.ts create mode 100644 tests/unit/components/text/TextInput.test.ts create mode 100644 tests/unit/components/ui/Alignment.test.ts create mode 100644 tests/unit/components/ui/Badge.test.ts create mode 100644 tests/unit/components/ui/Button.test.ts create mode 100644 tests/unit/components/ui/ColorPicker.test.ts create mode 100644 tests/unit/components/ui/RangeSlider.test.ts create mode 100644 tests/unit/components/ui/SelectFont.test.ts create mode 100644 tests/unit/components/ui/__mocks__/MockIcon.svelte 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 @@ + + +