343 lines
10 KiB
TypeScript
343 lines
10 KiB
TypeScript
import type { Panel } from "$lib/types/panel";
|
|
import { PanelStorage } from "$lib/utils/panelStorage";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
// Mock the error handler to avoid console noise during tests
|
|
vi.mock("$lib/utils/errorHandler", () => ({
|
|
logError: vi.fn(),
|
|
handleError: vi.fn((error: any, message: string) => `${message}: ${error}`),
|
|
}));
|
|
|
|
describe("PanelStorage", () => {
|
|
let storage: PanelStorage;
|
|
let mockPanel: Panel;
|
|
let mockLocalStorage: any;
|
|
|
|
beforeEach(() => {
|
|
storage = new PanelStorage();
|
|
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"),
|
|
};
|
|
|
|
// Create a properly typed mock for localStorage
|
|
mockLocalStorage = {
|
|
getItem: vi.fn(),
|
|
setItem: vi.fn(),
|
|
removeItem: vi.fn(),
|
|
clear: vi.fn(),
|
|
length: 0,
|
|
key: vi.fn(),
|
|
};
|
|
|
|
// Replace window.localStorage with our mock
|
|
Object.defineProperty(window, "localStorage", {
|
|
value: mockLocalStorage,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("savePanel", () => {
|
|
it("should save a new panel successfully", () => {
|
|
const result = storage.savePanel(mockPanel);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockLocalStorage.setItem).toHaveBeenCalledWith("twitch-panels", expect.stringContaining("test-panel-1"));
|
|
});
|
|
|
|
it("should update existing panel", () => {
|
|
// Save initial panel
|
|
storage.savePanel(mockPanel);
|
|
|
|
// Update panel
|
|
const updatedPanel = { ...mockPanel, height: 150 };
|
|
const result = storage.savePanel(updatedPanel);
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
// Get the last call to localStorage.setItem
|
|
const calls = mockLocalStorage.setItem.mock.calls;
|
|
const savedData = JSON.parse(calls[calls.length - 1][1]);
|
|
expect(savedData[0].height).toBe(150);
|
|
});
|
|
|
|
it("should limit panels to MAX_PANELS count", () => {
|
|
// Create and save 51 panels (exceeding MAX_PANELS = 50)
|
|
const panels = Array.from({ length: 51 }, (_, i) => ({
|
|
...mockPanel,
|
|
id: `panel-${i}`,
|
|
}));
|
|
|
|
// Mock localStorage to return the saved data for getAllPanels
|
|
let savedData: any[] = [];
|
|
mockLocalStorage.setItem.mockImplementation((key: string, value: string) => {
|
|
if (key === "twitch-panels") {
|
|
savedData = JSON.parse(value);
|
|
}
|
|
});
|
|
|
|
mockLocalStorage.getItem.mockImplementation((key: string) => {
|
|
if (key === "twitch-panels") {
|
|
return JSON.stringify(savedData);
|
|
}
|
|
return null;
|
|
});
|
|
|
|
panels.forEach((panel) => storage.savePanel(panel));
|
|
|
|
// Check the final state by calling getAllPanels
|
|
const finalPanels = storage.getAllPanels();
|
|
expect(finalPanels.length).toBe(50);
|
|
expect(finalPanels[0].id).toBe("panel-50"); // Most recent panel
|
|
});
|
|
|
|
it("should handle localStorage errors gracefully", () => {
|
|
// Mock localStorage to throw an error
|
|
mockLocalStorage.setItem.mockImplementation(() => {
|
|
throw new Error("Storage full");
|
|
});
|
|
|
|
const result = storage.savePanel(mockPanel);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain("Ошибка сохранения панели");
|
|
});
|
|
});
|
|
|
|
describe("getAllPanels", () => {
|
|
it("should return empty array when no panels exist", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(null);
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toEqual([]);
|
|
});
|
|
|
|
it("should return saved panels", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(1);
|
|
expect(panels[0]).toEqual(mockPanel);
|
|
});
|
|
|
|
it("should filter out invalid panels", () => {
|
|
const validPanel = mockPanel;
|
|
const invalidPanel = {
|
|
id: "invalid-1",
|
|
backgroundImage: "/backgrounds/b2.jpg",
|
|
// Missing required text property
|
|
height: 100,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
};
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([validPanel, invalidPanel]));
|
|
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(1);
|
|
expect(panels[0]).toEqual(validPanel);
|
|
});
|
|
|
|
it("should handle corrupted localStorage data", () => {
|
|
mockLocalStorage.getItem.mockReturnValue("invalid json");
|
|
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("getPanelById", () => {
|
|
it("should return panel by id", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
const panel = storage.getPanelById("test-panel-1");
|
|
|
|
expect(panel).toEqual(mockPanel);
|
|
});
|
|
|
|
it("should return undefined for non-existent panel", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
const panel = storage.getPanelById("non-existent");
|
|
|
|
expect(panel).toBeUndefined();
|
|
});
|
|
|
|
it("should handle errors gracefully", () => {
|
|
// Mock localStorage to throw an error
|
|
mockLocalStorage.getItem.mockImplementation(() => {
|
|
throw new Error("Storage error");
|
|
});
|
|
|
|
const panel = storage.getPanelById("test-panel-1");
|
|
|
|
expect(panel).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("deletePanel", () => {
|
|
it("should delete panel by id", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
const result = storage.deletePanel("test-panel-1");
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockLocalStorage.setItem).toHaveBeenCalledWith("twitch-panels", "[]");
|
|
});
|
|
|
|
it("should succeed when panel does not exist", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
const result = storage.deletePanel("non-existent");
|
|
|
|
expect(result.success).toBe(true);
|
|
// Should still save the unchanged array
|
|
expect(mockLocalStorage.setItem).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle localStorage errors", () => {
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
|
|
// Mock localStorage to throw an error
|
|
mockLocalStorage.setItem.mockImplementation(() => {
|
|
throw new Error("Storage error");
|
|
});
|
|
|
|
const result = storage.deletePanel("test-panel-1");
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain("Ошибка удаления панели");
|
|
});
|
|
});
|
|
|
|
describe("clearAll", () => {
|
|
it("should clear all panels", () => {
|
|
const result = storage.clearAll();
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockLocalStorage.removeItem).toHaveBeenCalledWith("twitch-panels");
|
|
});
|
|
|
|
it("should handle localStorage errors", () => {
|
|
// Mock localStorage to throw an error
|
|
mockLocalStorage.removeItem.mockImplementation(() => {
|
|
throw new Error("Storage error");
|
|
});
|
|
|
|
const result = storage.clearAll();
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain("Ошибка очистки хранилища");
|
|
});
|
|
});
|
|
|
|
describe("getPanelCount", () => {
|
|
it("should return correct panel count", () => {
|
|
expect(storage.getPanelCount()).toBe(0);
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
expect(storage.getPanelCount()).toBe(1);
|
|
|
|
const secondPanel = { ...mockPanel, id: "test-panel-2" };
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel, secondPanel]));
|
|
expect(storage.getPanelCount()).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("hasSpaceForNewPanel", () => {
|
|
it("should return true when under limit", () => {
|
|
expect(storage.hasSpaceForNewPanel()).toBe(true);
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([mockPanel]));
|
|
expect(storage.hasSpaceForNewPanel()).toBe(true);
|
|
});
|
|
|
|
it("should return false when at limit", () => {
|
|
// Create and save 50 panels (MAX_PANELS limit)
|
|
const panels = Array.from({ length: 50 }, (_, i) => ({
|
|
...mockPanel,
|
|
id: `panel-${i}`,
|
|
}));
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify(panels));
|
|
|
|
expect(storage.hasSpaceForNewPanel()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("panel validation", () => {
|
|
it("should validate correct panel structure", () => {
|
|
const validPanel = mockPanel;
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([validPanel]));
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(1);
|
|
expect(panels[0]).toEqual(validPanel);
|
|
});
|
|
|
|
it("should reject panel with invalid height", () => {
|
|
const invalidPanel = {
|
|
...mockPanel,
|
|
height: -100, // Negative height
|
|
};
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel]));
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(0);
|
|
});
|
|
|
|
it("should reject panel with height exceeding maximum", () => {
|
|
const invalidPanel = {
|
|
...mockPanel,
|
|
height: 2000, // Exceeds PANEL_HEIGHT_MAX (1000)
|
|
};
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel]));
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(0);
|
|
});
|
|
|
|
it("should reject panel with missing required properties", () => {
|
|
const invalidPanel = {
|
|
id: "invalid-panel",
|
|
// Missing backgroundImage
|
|
text: mockPanel.text,
|
|
height: 100,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any;
|
|
|
|
mockLocalStorage.getItem.mockReturnValue(JSON.stringify([invalidPanel]));
|
|
const panels = storage.getAllPanels();
|
|
|
|
expect(panels).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|