test(app-frontend): configure vitest and implement state and component test suite
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import { render, screen, act } from "@testing-library/react";
|
||||||
|
import { expect, test, vi, beforeEach } from "vitest";
|
||||||
|
import App from "./App";
|
||||||
|
import { api } from "./api";
|
||||||
|
|
||||||
|
vi.mock("./api", () => {
|
||||||
|
return {
|
||||||
|
api: {
|
||||||
|
getInitialState: vi.fn(),
|
||||||
|
showSettingsWindow: vi.fn(),
|
||||||
|
updateConfig: vi.fn(),
|
||||||
|
getHotkeyStatus: vi.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mounts App and fetches initial state", async () => {
|
||||||
|
const mockInitialPayload = {
|
||||||
|
config: {
|
||||||
|
strip_utms: true,
|
||||||
|
fix_quotes: true,
|
||||||
|
fix_newlines: true,
|
||||||
|
play_sound: true,
|
||||||
|
autostart: false,
|
||||||
|
hotkey: "Alt+Shift+C",
|
||||||
|
custom_params: [],
|
||||||
|
},
|
||||||
|
hotkey_conflict: false,
|
||||||
|
hwid: "device-123",
|
||||||
|
is_licensed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(api.getInitialState).mockResolvedValue(mockInitialPayload);
|
||||||
|
vi.mocked(api.showSettingsWindow).mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<App />);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(api.getInitialState).toHaveBeenCalledTimes(1);
|
||||||
|
expect(api.showSettingsWindow).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(screen.getByText("Alt")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Shift")).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText("C").length).toBe(2); // One in logo, one in keycap
|
||||||
|
expect(screen.getByText("Play sound on clean")).toBeInTheDocument();
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { expect, test, vi } from "vitest";
|
||||||
|
import { GeneralTab } from "./GeneralTab";
|
||||||
|
import { CleanConfig } from "../types";
|
||||||
|
|
||||||
|
const mockConfig: CleanConfig = {
|
||||||
|
strip_utms: true,
|
||||||
|
fix_quotes: true,
|
||||||
|
fix_newlines: true,
|
||||||
|
play_sound: true,
|
||||||
|
autostart: false,
|
||||||
|
hotkey: "Alt+Shift+C",
|
||||||
|
custom_params: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
test("renders hotkey display and toggles", () => {
|
||||||
|
const onToggleRecording = vi.fn();
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<GeneralTab
|
||||||
|
config={mockConfig}
|
||||||
|
isConflict={false}
|
||||||
|
isRecording={false}
|
||||||
|
onToggleRecording={onToggleRecording}
|
||||||
|
onUpdateConfig={onUpdateConfig}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Alt")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Shift")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("C")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const hotkeyZone = screen.getByTestId("hotkey-display");
|
||||||
|
|
||||||
|
expect(hotkeyZone).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(hotkeyZone);
|
||||||
|
|
||||||
|
expect(onToggleRecording).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const soundCheckbox = screen.getAllByRole("checkbox")[0];
|
||||||
|
|
||||||
|
fireEvent.click(soundCheckbox);
|
||||||
|
|
||||||
|
expect(onUpdateConfig).toHaveBeenCalledWith({ play_sound: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows conflict text when isConflict is true", () => {
|
||||||
|
render(
|
||||||
|
<GeneralTab
|
||||||
|
config={mockConfig}
|
||||||
|
isConflict={true}
|
||||||
|
isRecording={false}
|
||||||
|
onToggleRecording={() => {}}
|
||||||
|
onUpdateConfig={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/conflict/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { expect, test, vi } from "vitest";
|
||||||
|
import { LicenseTab } from "./LicenseTab";
|
||||||
|
|
||||||
|
test("renders licensed status when licensed", () => {
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={true}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput=""
|
||||||
|
isActivating={false}
|
||||||
|
error=""
|
||||||
|
onKeyInputChange={() => {}}
|
||||||
|
onActivate={() => {}}
|
||||||
|
onOfflineActivate={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: /activate/i }),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("hw-123")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders activation form when unlicensed", () => {
|
||||||
|
const onKeyInputChange = vi.fn();
|
||||||
|
const onActivate = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={false}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput="my-key"
|
||||||
|
isActivating={false}
|
||||||
|
error="Invalid key"
|
||||||
|
onKeyInputChange={onKeyInputChange}
|
||||||
|
onActivate={onActivate}
|
||||||
|
onOfflineActivate={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Unregistered")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText(/enter license key.../i);
|
||||||
|
|
||||||
|
expect(input).toHaveValue("my-key");
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "new-key" } });
|
||||||
|
|
||||||
|
expect(onKeyInputChange).toHaveBeenCalledWith("new-key");
|
||||||
|
|
||||||
|
expect(screen.getByText("Invalid key")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const button = screen.getByRole("button", { name: "Activate" });
|
||||||
|
|
||||||
|
fireEvent.click(button);
|
||||||
|
|
||||||
|
expect(onActivate).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders spinner and disables input when activating", () => {
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={false}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput="my-key"
|
||||||
|
isActivating={true}
|
||||||
|
error=""
|
||||||
|
onKeyInputChange={() => {}}
|
||||||
|
onActivate={() => {}}
|
||||||
|
onOfflineActivate={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByPlaceholderText(/enter license key.../i)).toBeDisabled();
|
||||||
|
expect(screen.getByRole("button", { name: /activating/i })).toBeDisabled();
|
||||||
|
expect(screen.getByText("Activating...")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("switches to offline mode when toggle clicked", () => {
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={false}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput=""
|
||||||
|
isActivating={false}
|
||||||
|
error=""
|
||||||
|
onKeyInputChange={() => {}}
|
||||||
|
onActivate={() => {}}
|
||||||
|
onOfflineActivate={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = screen.getByText("Have an offline token?");
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByPlaceholderText(/paste offline token/i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByPlaceholderText(/enter license key.../i),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls onOfflineActivate with token when offline mode submitted", () => {
|
||||||
|
const onOfflineActivate = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={false}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput=""
|
||||||
|
isActivating={false}
|
||||||
|
error=""
|
||||||
|
onKeyInputChange={() => {}}
|
||||||
|
onActivate={() => {}}
|
||||||
|
onOfflineActivate={onOfflineActivate}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Have an offline token?"));
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText(/paste offline token/i);
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "test.payload.signature" } });
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Verify Token" }));
|
||||||
|
|
||||||
|
expect(onOfflineActivate).toHaveBeenCalledWith("test.payload.signature");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("switches back to online mode from offline mode", () => {
|
||||||
|
render(
|
||||||
|
<LicenseTab
|
||||||
|
isLicensed={false}
|
||||||
|
hwid="hw-123"
|
||||||
|
keyInput=""
|
||||||
|
isActivating={false}
|
||||||
|
error=""
|
||||||
|
onKeyInputChange={() => {}}
|
||||||
|
onActivate={() => {}}
|
||||||
|
onOfflineActivate={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Have an offline token?"));
|
||||||
|
expect(
|
||||||
|
screen.getByPlaceholderText(/paste offline token/i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Activate with license key instead"));
|
||||||
|
expect(
|
||||||
|
screen.getByPlaceholderText(/enter license key.../i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByPlaceholderText(/paste offline token/i),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { render, screen, fireEvent, act } from "@testing-library/react";
|
||||||
|
import { expect, test, vi, afterEach } from "vitest";
|
||||||
|
import { RulesTab } from "./RulesTab";
|
||||||
|
import { CleanConfig } from "../types";
|
||||||
|
|
||||||
|
const mockConfig: CleanConfig = {
|
||||||
|
strip_utms: true,
|
||||||
|
fix_quotes: false,
|
||||||
|
fix_newlines: true,
|
||||||
|
play_sound: true,
|
||||||
|
autostart: false,
|
||||||
|
hotkey: "Alt+Shift+C",
|
||||||
|
custom_params: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders rule options and calls toggles", () => {
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
expect(screen.getByText("Strip tracking parameters")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Normalize typography")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Clean trailing newlines")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const checkboxes = screen.getAllByRole("checkbox");
|
||||||
|
|
||||||
|
expect(checkboxes[0]).toBeChecked();
|
||||||
|
expect(checkboxes[1]).not.toBeChecked();
|
||||||
|
expect(checkboxes[2]).toBeChecked();
|
||||||
|
|
||||||
|
fireEvent.click(checkboxes[1]);
|
||||||
|
|
||||||
|
expect(onUpdateConfig).toHaveBeenCalledWith({ fix_quotes: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom params input is visible when strip_utms is enabled", () => {
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByPlaceholderText("ref, tracking_id, campaign"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom params input is hidden when strip_utms is disabled", () => {
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
const configDisabled = { ...mockConfig, strip_utms: false };
|
||||||
|
|
||||||
|
render(<RulesTab config={configDisabled} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByPlaceholderText("ref, tracking_id, campaign"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("typing shows debouncing indicator", () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("ref, tracking_id, campaign");
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "ref" } });
|
||||||
|
|
||||||
|
expect(screen.getByText("...")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("debouncing indicator disappears after debounce completes", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("ref, tracking_id, campaign");
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "ref" } });
|
||||||
|
|
||||||
|
expect(screen.getByText("...")).toBeInTheDocument();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByText("...")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("\u2713")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("saved indicator fades away after 1.5 seconds", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("ref, tracking_id, campaign");
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "ref" } });
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("\u2713")).toBeInTheDocument();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
vi.advanceTimersByTime(1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByText("\u2713")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rapid keystrokes only trigger one config update", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const onUpdateConfig = vi.fn();
|
||||||
|
|
||||||
|
render(<RulesTab config={mockConfig} onUpdateConfig={onUpdateConfig} />);
|
||||||
|
|
||||||
|
const input = screen.getByPlaceholderText("ref, tracking_id, campaign");
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value: "r" } });
|
||||||
|
fireEvent.change(input, { target: { value: "re" } });
|
||||||
|
fireEvent.change(input, { target: { value: "ref" } });
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
vi.advanceTimersByTime(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onUpdateConfig).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onUpdateConfig).toHaveBeenCalledWith({
|
||||||
|
custom_params: ["ref"],
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { expect, test, vi } from "vitest";
|
||||||
|
import { TabHeaders } from "./TabHeaders";
|
||||||
|
|
||||||
|
test("renders tab headers and handles switching", () => {
|
||||||
|
const onTabChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TabHeaders
|
||||||
|
activeTab="general"
|
||||||
|
isLicensed={true}
|
||||||
|
onTabChange={onTabChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("General")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Rules")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("License")).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("Rules"));
|
||||||
|
|
||||||
|
expect(onTabChange).toHaveBeenCalledWith("rules");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shows warning badge on License tab when unlicensed", () => {
|
||||||
|
render(
|
||||||
|
<TabHeaders
|
||||||
|
activeTab="license"
|
||||||
|
isLicensed={false}
|
||||||
|
onTabChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("License ⚠️")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("General and Rules tabs are disabled when unlicensed", () => {
|
||||||
|
render(
|
||||||
|
<TabHeaders
|
||||||
|
activeTab="license"
|
||||||
|
isLicensed={false}
|
||||||
|
onTabChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("tab", { name: "General" })).toBeDisabled();
|
||||||
|
expect(screen.getByRole("tab", { name: "Rules" })).toBeDisabled();
|
||||||
|
expect(screen.getByRole("tab", { name: /License/i })).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clicking disabled tabs when unlicensed does not fire onTabChange", () => {
|
||||||
|
const onTabChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TabHeaders
|
||||||
|
activeTab="license"
|
||||||
|
isLicensed={false}
|
||||||
|
onTabChange={onTabChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "General" }));
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "Rules" }));
|
||||||
|
|
||||||
|
expect(onTabChange).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { expect, test, vi } from "vitest";
|
||||||
|
import { ToggleSwitch } from "./ToggleSwitch";
|
||||||
|
|
||||||
|
test("renders unchecked", () => {
|
||||||
|
render(<ToggleSwitch checked={false} onChange={() => {}} />);
|
||||||
|
expect(screen.getByRole("checkbox")).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("renders checked", () => {
|
||||||
|
render(<ToggleSwitch checked={true} onChange={() => {}} />);
|
||||||
|
expect(screen.getByRole("checkbox")).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls onChange with true when clicked unchecked", () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
|
||||||
|
render(<ToggleSwitch checked={false} onChange={onChange} />);
|
||||||
|
fireEvent.click(screen.getByRole("checkbox"));
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("calls onChange with false when clicked checked", () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
|
||||||
|
render(<ToggleSwitch checked={true} onChange={onChange} />);
|
||||||
|
fireEvent.click(screen.getByRole("checkbox"));
|
||||||
|
|
||||||
|
expect(onChange).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import { expect, test } from "vitest";
|
||||||
|
import { appReducer, initialAppState } from "./reducer";
|
||||||
|
import { CleanConfig } from "./types";
|
||||||
|
|
||||||
|
const mockConfig: CleanConfig = {
|
||||||
|
strip_utms: false,
|
||||||
|
fix_quotes: false,
|
||||||
|
fix_newlines: false,
|
||||||
|
play_sound: false,
|
||||||
|
autostart: true,
|
||||||
|
hotkey: "Ctrl+Shift+D",
|
||||||
|
custom_params: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
test("INITIALIZE action when licensed", () => {
|
||||||
|
const result = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: true,
|
||||||
|
hwid: "device-123",
|
||||||
|
isLicensed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isLicensed).toBe(true);
|
||||||
|
expect(result.hwid).toBe("device-123");
|
||||||
|
expect(result.config.hotkey).toBe("Ctrl+Shift+D");
|
||||||
|
expect(result.tabState.tab).toBe("general");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("INITIALIZE action when unlicensed", () => {
|
||||||
|
const result = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "device-123",
|
||||||
|
isLicensed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isLicensed).toBe(false);
|
||||||
|
expect(result.tabState.tab).toBe("license");
|
||||||
|
|
||||||
|
if (result.tabState.tab === "license") {
|
||||||
|
expect(result.tabState.keyInput).toBe("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SET_TAB transition", () => {
|
||||||
|
const licensedState = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "",
|
||||||
|
isLicensed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = appReducer(licensedState, {
|
||||||
|
type: "SET_TAB",
|
||||||
|
payload: "rules",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.tabState.tab).toBe("rules");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SET_CONFIG partial update", () => {
|
||||||
|
const state = appReducer(initialAppState, {
|
||||||
|
type: "SET_CONFIG",
|
||||||
|
payload: { play_sound: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.config.play_sound).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SET_RECORDING on general tab", () => {
|
||||||
|
let state = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "",
|
||||||
|
isLicensed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
state = appReducer(state, {
|
||||||
|
type: "SET_TAB",
|
||||||
|
payload: "general",
|
||||||
|
});
|
||||||
|
|
||||||
|
state = appReducer(state, {
|
||||||
|
type: "SET_RECORDING",
|
||||||
|
payload: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.tabState.tab).toBe("general");
|
||||||
|
|
||||||
|
if (state.tabState.tab === "general") {
|
||||||
|
expect(state.tabState.isRecording).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ACTIVATION lifecycle", () => {
|
||||||
|
let state = appReducer(initialAppState, {
|
||||||
|
type: "SET_TAB",
|
||||||
|
payload: "license",
|
||||||
|
});
|
||||||
|
|
||||||
|
state = appReducer(state, {
|
||||||
|
type: "SET_LICENSE_KEY_INPUT",
|
||||||
|
payload: "test-key",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.tabState.tab === "license") {
|
||||||
|
expect(state.tabState.keyInput).toBe("test-key");
|
||||||
|
}
|
||||||
|
|
||||||
|
state = appReducer(state, { type: "START_ACTIVATION" });
|
||||||
|
|
||||||
|
if (state.tabState.tab === "license") {
|
||||||
|
expect(state.tabState.isActivating).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = appReducer(state, {
|
||||||
|
type: "ACTIVATION_FAILURE",
|
||||||
|
payload: "Invalid Key",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.tabState.tab === "license") {
|
||||||
|
expect(state.tabState.isActivating).toBe(false);
|
||||||
|
expect(state.tabState.error).toBe("Invalid Key");
|
||||||
|
}
|
||||||
|
|
||||||
|
state = appReducer(state, { type: "ACTIVATION_SUCCESS" });
|
||||||
|
|
||||||
|
expect(state.isLicensed).toBe(true);
|
||||||
|
expect(state.tabState.tab).toBe("license");
|
||||||
|
|
||||||
|
if (state.tabState.tab === "license") {
|
||||||
|
expect(state.tabState.keyInput).toBe("");
|
||||||
|
expect(state.tabState.isActivating).toBe(false);
|
||||||
|
expect(state.tabState.error).toBe("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SET_TAB to general is blocked when unlicensed", () => {
|
||||||
|
const unlicensedState = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "",
|
||||||
|
isLicensed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unlicensedState.tabState.tab).toBe("license");
|
||||||
|
|
||||||
|
const after = appReducer(unlicensedState, {
|
||||||
|
type: "SET_TAB",
|
||||||
|
payload: "general",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(after.tabState.tab).toBe("license");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SET_TAB to rules is blocked when unlicensed", () => {
|
||||||
|
const unlicensedState = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "",
|
||||||
|
isLicensed: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const after = appReducer(unlicensedState, {
|
||||||
|
type: "SET_TAB",
|
||||||
|
payload: "rules",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(after.tabState.tab).toBe("license");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("updating config does not reset active tab", () => {
|
||||||
|
let state = appReducer(initialAppState, {
|
||||||
|
type: "INITIALIZE",
|
||||||
|
payload: {
|
||||||
|
config: mockConfig,
|
||||||
|
hotkeyConflict: false,
|
||||||
|
hwid: "",
|
||||||
|
isLicensed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
state = appReducer(state, { type: "SET_TAB", payload: "rules" });
|
||||||
|
|
||||||
|
expect(state.tabState.tab).toBe("rules");
|
||||||
|
|
||||||
|
state = appReducer(state, { type: "SET_HOTKEY_CONFLICT", payload: false });
|
||||||
|
|
||||||
|
expect(state.tabState.tab).toBe("rules");
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
// @ts-expect-error process is a nodejs global
|
||||||
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: "jsdom",
|
||||||
|
setupFiles: "./src/test/setup.ts",
|
||||||
|
},
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
host: host || false,
|
||||||
|
hmr: host
|
||||||
|
? {
|
||||||
|
protocol: "ws",
|
||||||
|
host,
|
||||||
|
port: 1421,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user