test: update coverage, add some tests

This commit is contained in:
2026-02-05 10:21:09 +05:00
parent 6d608c847a
commit 1e1ad05ae5
8 changed files with 979 additions and 49 deletions
+297
View File
@@ -0,0 +1,297 @@
import ImageUpload from "$components/image/ImageUpload.svelte";
import { fireEvent, render, screen, waitFor } from "@testing-library/svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Mock the stores and services
vi.mock("$stores/uiStore", () => ({
uiStore: {
subscribe: vi.fn((callback) => {
callback({ error: null, loading: false });
return () => {};
}),
},
setCurrentStep: vi.fn(),
setLoading: vi.fn(),
clearError: vi.fn(),
}));
vi.mock("$services/imageService", () => ({
imageService: {
validateAndProcessImage: vi.fn(),
},
}));
vi.mock("$lib/utils/errorHandler", () => ({
handleError: vi.fn((error) => `Error: ${error}`),
}));
describe("ImageUpload", () => {
let mockOnImageSelect: (image: string) => void;
let mockImageService: any;
beforeEach(() => {
mockOnImageSelect = vi.fn();
vi.clearAllMocks();
// Setup mock image service
mockImageService = {
validateAndProcessImage: vi.fn().mockResolvedValue({
success: true,
imageUrl: "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
}),
};
});
it("should render upload component", () => {
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
expect(container).toBeTruthy();
});
it("should handle file selection via input", async () => {
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
// Create a mock file
const mockFile = new File(["test image content"], "test.jpg", {
type: "image/jpeg",
});
// Find file input
const fileInput = container.querySelector('input[type="file"]');
expect(fileInput).toBeTruthy();
if (fileInput) {
// Simulate file selection
await fireEvent.change(fileInput, {
target: { files: [mockFile] },
});
// Should call image service
await waitFor(() => {
expect(mockImageService.validateAndProcessImage).toHaveBeenCalled();
});
}
});
it("should handle drag and drop", async () => {
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
// Create mock file
const mockFile = new File(["test image content"], "test.jpg", {
type: "image/jpeg",
});
// Find drop zone
const dropZone = container.querySelector("[data-testid='drop-zone'], .drop-zone, .upload-area");
if (dropZone) {
// Simulate drag over
await fireEvent.dragOver(dropZone, {
dataTransfer: { files: [mockFile] },
});
// Simulate drop
await fireEvent.drop(dropZone, {
dataTransfer: { files: [mockFile] },
});
// Should process the image
await waitFor(() => {
expect(mockImageService.validateAndProcessImage).toHaveBeenCalled();
});
}
});
it("should handle URL input", async () => {
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
// Find URL input and toggle button
const urlInputs = container.querySelectorAll('input[type="url"], input[placeholder*="URL"]');
const buttons = screen.getAllByRole("button");
// Toggle URL input if needed
const toggleButton = buttons.find(
(button) =>
button.textContent?.includes("URL") ||
button.textContent?.includes("Ссылка") ||
button.textContent?.includes("By URL"),
);
if (toggleButton) {
await fireEvent.click(toggleButton);
}
if (urlInputs.length > 0) {
const testUrl = "https://example.com/image.jpg";
await fireEvent.input(urlInputs[0], { target: { value: testUrl } });
// Submit URL form
const submitButton = buttons.find((button) => {
const isLoadButton = button.textContent?.includes("Load") || button.textContent?.includes("Загрузить");
const buttonType = (button as HTMLButtonElement).type;
return isLoadButton || buttonType === "submit";
});
if (submitButton) {
await fireEvent.click(submitButton);
// Should process the URL
await waitFor(() => {
expect(mockImageService.validateAndProcessImage).toHaveBeenCalled();
});
}
}
});
it("should handle paste event", async () => {
render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
// Create mock clipboard data
const mockClipboardData = {
items: [
{
kind: "file",
type: "image/jpeg",
getAsFile: () => new File(["pasted image"], "pasted.jpg", { type: "image/jpeg" }),
},
],
};
// Simulate paste event
const pasteEvent = new ClipboardEvent("paste", {
clipboardData: mockClipboardData as any,
});
document.dispatchEvent(pasteEvent);
// Should process the pasted image
await waitFor(() => {
expect(mockImageService.validateAndProcessImage).toHaveBeenCalled();
});
});
it("should show error for invalid image", async () => {
// Mock image service to return error
mockImageService.validateAndProcessImage.mockResolvedValue({
success: false,
error: "Invalid image format",
});
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
const mockFile = new File(["invalid content"], "test.txt", {
type: "text/plain",
});
const fileInput = container.querySelector('input[type="file"]');
if (fileInput) {
await fireEvent.change(fileInput, {
target: { files: [mockFile] },
});
// Should show error message
await waitFor(() => {
expect(container.textContent).toContain("Error");
});
}
});
it("should call onImageSelect when image is successfully processed", async () => {
const mockImageUrl = "data:image/jpeg;base64,/9j/4AAQSkZJRg==";
// Mock successful image processing
mockImageService.validateAndProcessImage.mockResolvedValue({
success: true,
imageUrl: mockImageUrl,
});
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
const mockFile = new File(["test image content"], "test.jpg", {
type: "image/jpeg",
});
const fileInput = container.querySelector('input[type="file"]');
if (fileInput) {
await fireEvent.change(fileInput, {
target: { files: [mockFile] },
});
// Should call onImageSelect with processed image
await waitFor(() => {
expect(mockOnImageSelect).toHaveBeenCalledWith(mockImageUrl);
});
}
});
it("should show loading state during processing", async () => {
const { setLoading } = await import("$stores/uiStore");
// Mock slow processing
mockImageService.validateAndProcessImage.mockImplementation(
() =>
new Promise((resolve) =>
setTimeout(
() =>
resolve({
success: true,
imageUrl: "data:image/jpeg;base64,/9j/4AAQSkZJRg==",
}),
100,
),
),
);
const { container } = render(ImageUpload, {
props: {
onImageSelect: mockOnImageSelect,
},
});
const mockFile = new File(["test image content"], "test.jpg", {
type: "image/jpeg",
});
const fileInput = container.querySelector('input[type="file"]');
if (fileInput) {
await fireEvent.change(fileInput, {
target: { files: [mockFile] },
});
// Should show loading state
expect(setLoading).toHaveBeenCalledWith(true);
// Wait for processing to complete
await waitFor(() => {
expect(setLoading).toHaveBeenCalledWith(false);
});
}
});
});
@@ -0,0 +1,99 @@
import PanelPreview from "$components/panel/PanelPreview.svelte";
import type { Panel } from "$lib/types/panel";
import { fireEvent, render, screen } from "@testing-library/svelte";
import type { Stage as KonvaStage } from "konva/lib/Stage";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Mock the UI store
vi.mock("$stores/uiStore", () => ({
setCurrentStep: vi.fn(),
}));
describe("PanelPreview", () => {
let mockPanel: Panel;
let mockOnDownload: (panel: Panel, konvaStage: KonvaStage) => void;
beforeEach(() => {
mockPanel = {
id: "test-panel-1",
backgroundImage: "/backgrounds/b1.jpg",
text: {
id: "test-text-1",
text: "Test Panel",
fontSize: 18,
fontFamily: "Arial",
color: "#ffffff",
textAlign: "center",
paddingX: 10,
verticalOffset: 0,
},
height: 100,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
};
mockOnDownload = vi.fn();
});
it("should render panel with text content", () => {
const { container } = render(PanelPreview, {
props: {
panel: mockPanel,
onDownload: mockOnDownload,
},
});
// Should render without errors
expect(container).toBeTruthy();
});
it("should call onDownload when download button is clicked", async () => {
render(PanelPreview, {
props: {
panel: mockPanel,
onDownload: mockOnDownload,
},
});
// Find and click download button - button text might vary, so we'll look for a button
const buttons = screen.getAllByRole("button");
const downloadButton = buttons.find(
(button) => button.textContent?.includes("Скачать") || button.textContent?.includes("Download"),
);
if (downloadButton) {
await fireEvent.click(downloadButton);
// Should call onDownload with panel and konva stage
expect(mockOnDownload).toHaveBeenCalledWith(mockPanel, expect.any(Object));
}
});
it("should handle panel without onDownload callback", () => {
const { container } = render(PanelPreview, {
props: {
panel: mockPanel,
},
});
// Should render without errors
expect(container).toBeTruthy();
});
it("should display panel text content", () => {
render(PanelPreview, {
props: {
panel: mockPanel,
onDownload: mockOnDownload,
},
});
// Check if panel text is rendered (this might be in a canvas, so we check container)
const { container } = render(PanelPreview, {
props: {
panel: mockPanel,
onDownload: mockOnDownload,
},
});
expect(container).toBeTruthy();
});
});
+242
View File
@@ -0,0 +1,242 @@
import TextManager from "$components/text/TextManager.svelte";
import { fireEvent, render, screen } from "@testing-library/svelte";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Mock the stores
vi.mock("$stores/panelStore", () => ({
textSettingsStore: {
subscribe: vi.fn((callback) => {
callback({
fontSize: 18,
fontFamily: "Arial",
color: "#ffffff",
textAlign: "center",
paddingX: 10,
verticalOffset: 0,
});
return () => {}; // unsubscribe function
}),
update: vi.fn(),
},
updateAllTextSettings: vi.fn(),
}));
describe("TextManager", () => {
let mockOnTextAdd: (text: string, settings?: any) => void;
let mockOnTextUpdate: (id: string, text: string) => void;
let mockOnTextDelete: (id: string) => void;
beforeEach(() => {
mockOnTextAdd = vi.fn();
mockOnTextUpdate = vi.fn();
mockOnTextDelete = vi.fn();
});
it("should render with empty text list", () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
expect(container).toBeTruthy();
});
it("should render with existing texts", () => {
const mockTexts = [
{ id: "text-1", text: "First text" },
{ id: "text-2", text: "Second text" },
];
const { container } = render(TextManager, {
props: {
texts: mockTexts,
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
expect(container).toBeTruthy();
});
it("should call onTextAdd when add text form is submitted", async () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find text input and add button
const textInputs = container.querySelectorAll('input[type="text"], textarea');
const buttons = screen.getAllByRole("button");
if (textInputs.length > 0 && buttons.length > 0) {
await fireEvent.input(textInputs[0], { target: { value: "New text" } });
await fireEvent.click(buttons[0]);
expect(mockOnTextAdd).toHaveBeenCalledWith("New text", expect.any(Object));
}
});
it("should show error for empty text", async () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find add button and click without entering text
const buttons = screen.getAllByRole("button");
if (buttons.length > 0) {
await fireEvent.click(buttons[0]);
// Should not call onTextAdd for empty text
expect(mockOnTextAdd).not.toHaveBeenCalled();
}
});
it("should handle text update", async () => {
const mockTexts = [{ id: "text-1", text: "Original text" }];
const { container } = render(TextManager, {
props: {
texts: mockTexts,
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find text inputs for existing texts
const textInputs = container.querySelectorAll('input[type="text"], textarea');
if (textInputs.length > 0) {
await fireEvent.input(textInputs[0], { target: { value: "Updated text" } });
// Should call onTextUpdate with the text ID and new text
expect(mockOnTextUpdate).toHaveBeenCalledWith("text-1", "Updated text");
}
});
it("should handle text deletion", async () => {
const mockTexts = [{ id: "text-1", text: "Text to delete" }];
render(TextManager, {
props: {
texts: mockTexts,
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find delete button (might be an X button or similar)
const buttons = screen.getAllByRole("button");
const deleteButton = buttons.find(
(button) =>
button.textContent?.includes("Delete") ||
button.textContent?.includes("Remove") ||
button.textContent?.includes("×") ||
button.textContent?.includes("X"),
);
if (deleteButton) {
await fireEvent.click(deleteButton);
expect(mockOnTextDelete).toHaveBeenCalledWith("text-1");
}
});
it("should handle font size changes", async () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find font size input (number input)
const numberInputs = container.querySelectorAll('input[type="number"]');
if (numberInputs.length > 0) {
await fireEvent.input(numberInputs[0], { target: { value: "24" } });
// Font size change should be handled in the component
expect(container).toBeTruthy(); // Basic check that component still renders
}
});
it("should handle color changes", async () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Find color input
const colorInputs = container.querySelectorAll('input[type="color"]');
if (colorInputs.length > 0) {
await fireEvent.input(colorInputs[0], { target: { value: "#ff0000" } });
// Color change should be handled in the component
expect(container).toBeTruthy(); // Basic check that component still renders
}
});
it("should handle text alignment changes", async () => {
const { container } = render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Look for alignment buttons or radio buttons
const radioButtons = screen.getAllByRole("radio");
if (radioButtons.length > 0) {
await fireEvent.click(radioButtons[0]);
expect(container).toBeTruthy(); // Basic check that component still renders
}
});
it("should call updateAllTextSettings when update all button is clicked", async () => {
const { updateAllTextSettings } = await import("$stores/panelStore");
render(TextManager, {
props: {
texts: [],
onTextAdd: mockOnTextAdd,
onTextUpdate: mockOnTextUpdate,
onTextDelete: mockOnTextDelete,
},
});
// Look for "Update All" or similar button
const buttons = screen.getAllByRole("button");
const updateAllButton = buttons.find(
(button) =>
button.textContent?.includes("Update All") ||
button.textContent?.includes("Apply to All") ||
button.textContent?.includes("Сохранить"),
);
if (updateAllButton) {
await fireEvent.click(updateAllButton);
expect(updateAllTextSettings).toHaveBeenCalled();
}
});
});