feat(KB-643): add Dashboard Scripts UI feature
- Add scripts field to ProjectSettings type for custom dashboard scripts - Update ScriptsModal with improved UI for script management - Add QuickScriptsDropdown component for quick script access - Implement scripts API routes for CRUD operations - Update CLI with multi-project commands (project add/remove/list/show/set-default/detect) - Various UI improvements and bug fixes across dashboard components - Migration support for multi-project architecture
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { ScriptsModal } from "../ScriptsModal";
|
||||
import type { ScriptEntry } from "../../api";
|
||||
|
||||
const mockScripts: Record<string, string> = {
|
||||
build: "npm run build",
|
||||
test: "pnpm test",
|
||||
lint: "eslint src/",
|
||||
lint: "eslint src --ext .ts,.tsx",
|
||||
};
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
fetchScripts: vi.fn(() => Promise.resolve({})),
|
||||
addScript: vi.fn(() => Promise.resolve({})),
|
||||
removeScript: vi.fn(() => Promise.resolve({})),
|
||||
addScript: vi.fn(() => Promise.resolve({ name: "new-script", command: "echo hello" })),
|
||||
removeScript: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
import { fetchScripts, addScript, removeScript } from "../../api";
|
||||
import {
|
||||
fetchScripts,
|
||||
addScript,
|
||||
removeScript,
|
||||
} from "../../api";
|
||||
|
||||
const onClose = vi.fn();
|
||||
const addToast = vi.fn();
|
||||
@@ -27,7 +32,7 @@ beforeEach(() => {
|
||||
describe("ScriptsModal", () => {
|
||||
it("does not render when closed", () => {
|
||||
const { container } = render(
|
||||
<ScriptsModal isOpen={false} onClose={onClose} addToast={addToast} />
|
||||
<ScriptsModal isOpen={false} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
expect(container.innerHTML).toBe("");
|
||||
});
|
||||
@@ -35,7 +40,9 @@ describe("ScriptsModal", () => {
|
||||
it("renders list of scripts", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("build")).toBeInTheDocument();
|
||||
@@ -47,286 +54,349 @@ describe("ScriptsModal", () => {
|
||||
it("shows empty state when no scripts exist", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
expect(screen.getByText(/No scripts defined yet/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("opens create form when Add button is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
expect(screen.getByText("New Script")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("validates script name - rejects empty name (button disabled)", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Fill in command only
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), {
|
||||
target: { value: "echo hello" },
|
||||
});
|
||||
|
||||
// Save button should be disabled when name is empty
|
||||
const saveButton = screen.getByTestId("save-script-btn") as HTMLButtonElement;
|
||||
expect(saveButton.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("validates script name - rejects invalid characters", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Fill in name with spaces
|
||||
fireEvent.change(screen.getByTestId("script-name-input"), {
|
||||
target: { value: "my script" },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), {
|
||||
target: { value: "echo hello" },
|
||||
});
|
||||
|
||||
// Click save
|
||||
fireEvent.click(screen.getByTestId("save-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-validation-error")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Name must be alphanumeric with hyphens and underscores only/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("validates script name - rejects reserved names", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Fill in reserved name
|
||||
fireEvent.change(screen.getByTestId("script-name-input"), {
|
||||
target: { value: "run" },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), {
|
||||
target: { value: "echo hello" },
|
||||
});
|
||||
|
||||
// Click save
|
||||
fireEvent.click(screen.getByTestId("save-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-validation-error")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Script name 'run' is reserved/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a new script successfully", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
vi.mocked(addScript).mockResolvedValueOnce({ deploy: "npm run deploy" });
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("scripts-empty-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Fill in form
|
||||
fireEvent.change(screen.getByTestId("script-name-input"), {
|
||||
target: { value: "deploy" },
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), {
|
||||
target: { value: "npm run deploy" },
|
||||
});
|
||||
|
||||
// Click save
|
||||
fireEvent.click(screen.getByTestId("save-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addScript).toHaveBeenCalledWith("deploy", "npm run deploy");
|
||||
expect(addToast).toHaveBeenCalledWith("Script 'deploy' created", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("runs a script when run button is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
|
||||
render(
|
||||
<ScriptsModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
addToast={addToast}
|
||||
onRunScript={onRunScript}
|
||||
/>
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("build")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("empty-state")).toBeInTheDocument();
|
||||
});
|
||||
// Use getAllByText since the header also shows "No scripts defined" text
|
||||
expect(screen.getAllByText(/No scripts defined/).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("opens create form when Add Script button is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
expect(screen.getByTestId("script-name-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("script-command-input")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("submits new script", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce({})
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
const nameInput = screen.getByTestId("script-name-input");
|
||||
const commandInput = screen.getByTestId("script-command-input");
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: "new-script" } });
|
||||
fireEvent.change(commandInput, { target: { value: "echo hello" } });
|
||||
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addScript).toHaveBeenCalledWith("new-script", "echo hello");
|
||||
expect(addToast).toHaveBeenCalledWith("Script created", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("validates script name (alphanumeric, hyphens, underscores only)", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
const nameInput = screen.getByTestId("script-name-input");
|
||||
fireEvent.change(nameInput, { target: { value: "invalid name with spaces" } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-name-error")).toBeInTheDocument();
|
||||
});
|
||||
// Verify the error message contains expected text
|
||||
expect(screen.getByTestId("script-name-error").textContent).toContain("letters");
|
||||
});
|
||||
|
||||
it("allows valid script names with hyphens and underscores", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce({})
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
const nameInput = screen.getByTestId("script-name-input");
|
||||
const commandInput = screen.getByTestId("script-command-input");
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: "my-script_v2" } });
|
||||
fireEvent.change(commandInput, { target: { value: "echo test" } });
|
||||
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addScript).toHaveBeenCalledWith("my-script_v2", "echo test");
|
||||
});
|
||||
});
|
||||
|
||||
it("runs script when Run button is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("run-script-build")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("run-script-build"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onRunScript).toHaveBeenCalledWith("build", "npm run build");
|
||||
});
|
||||
expect(onRunScript).toHaveBeenCalledWith("build", "npm run build");
|
||||
});
|
||||
|
||||
it("deletes a script with confirmation", async () => {
|
||||
it("shows delete confirmation", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
vi.mocked(removeScript).mockResolvedValueOnce({ test: "pnpm test", lint: "eslint src/" });
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("build")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("run-script-build")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click delete button
|
||||
fireEvent.click(screen.getByTestId("delete-script-build"));
|
||||
|
||||
// Should show confirm/cancel buttons
|
||||
// Confirm delete buttons should appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Confirm delete")).toBeInTheDocument();
|
||||
expect(screen.getByTitle("Cancel delete")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("confirm-delete-script-build")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("cancel-delete-script-build")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes script when confirmed", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce(mockScripts)
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("delete-script-build")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click confirm
|
||||
fireEvent.click(screen.getByTitle("Confirm delete"));
|
||||
// Click delete button
|
||||
fireEvent.click(screen.getByTestId("delete-script-build"));
|
||||
|
||||
// Confirm delete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("confirm-delete-script-build")).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByTestId("confirm-delete-script-build"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(removeScript).toHaveBeenCalledWith("build");
|
||||
expect(addToast).toHaveBeenCalledWith("Script 'build' deleted", "success");
|
||||
expect(addToast).toHaveBeenCalledWith("Script deleted", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("cancels delete when cancel button is clicked", async () => {
|
||||
it("cancels delete when cancel is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("build")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("delete-script-build")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click delete button
|
||||
fireEvent.click(screen.getByTestId("delete-script-build"));
|
||||
|
||||
// Should show confirm/cancel buttons
|
||||
// Cancel delete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle("Cancel delete")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("cancel-delete-script-build")).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByTestId("cancel-delete-script-build"));
|
||||
|
||||
// Click cancel
|
||||
fireEvent.click(screen.getByTitle("Cancel delete"));
|
||||
|
||||
// Delete should not have been called
|
||||
expect(removeScript).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles API errors gracefully", async () => {
|
||||
vi.mocked(fetchScripts).mockRejectedValueOnce(new Error("Failed to fetch"));
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
// Script should still be visible, delete button back to normal
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("Failed to fetch", "error");
|
||||
expect(screen.getByTestId("delete-script-build")).toBeInTheDocument();
|
||||
expect(removeScript).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows loading state while fetching scripts", async () => {
|
||||
vi.mocked(fetchScripts).mockImplementation(
|
||||
() => new Promise((resolve) => setTimeout(() => resolve({}), 100))
|
||||
it("shows error toast when API call fails", async () => {
|
||||
vi.mocked(fetchScripts).mockRejectedValueOnce(new Error("Network error"));
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
|
||||
expect(screen.getByTestId("scripts-loading")).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith(expect.stringContaining("Network error"), "error");
|
||||
});
|
||||
});
|
||||
|
||||
it("edits an existing script", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce(mockScripts);
|
||||
vi.mocked(addScript).mockResolvedValueOnce({ build: "npm run build:prod", test: "pnpm test" });
|
||||
it("cancels form when Cancel button is clicked", async () => {
|
||||
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||
|
||||
render(<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} />);
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("build")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-save-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("script-cancel-btn"));
|
||||
|
||||
// Form should be closed, back to list view
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("edits existing script", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce(mockScripts)
|
||||
.mockResolvedValueOnce(mockScripts);
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("edit-script-build")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click edit button
|
||||
fireEvent.click(screen.getByTestId("edit-script-build"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("script-form")).toBeInTheDocument();
|
||||
expect(screen.getByText("Edit Script")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("script-name-input")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("script-command-input")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Name should be disabled for editing
|
||||
const nameInput = screen.getByTestId("script-name-input") as HTMLInputElement;
|
||||
expect(nameInput.disabled).toBe(true);
|
||||
const commandInput = screen.getByTestId("script-command-input");
|
||||
fireEvent.change(commandInput, { target: { value: "npm run build:prod" } });
|
||||
|
||||
// Change command
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), {
|
||||
target: { value: "npm run build:prod" },
|
||||
});
|
||||
|
||||
// Click save
|
||||
fireEvent.click(screen.getByTestId("save-script-btn"));
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addScript).toHaveBeenCalledWith("build", "npm run build:prod");
|
||||
expect(addToast).toHaveBeenCalledWith("Script 'build' updated", "success");
|
||||
expect(addToast).toHaveBeenCalledWith("Script updated", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows validation error when script name is empty", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce({})
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
const commandInput = screen.getByTestId("script-command-input");
|
||||
fireEvent.change(commandInput, { target: { value: "echo test" } });
|
||||
|
||||
// Try to save with empty name
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("Script name is required", "error");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows validation error when command is empty", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce({})
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
const nameInput = screen.getByTestId("script-name-input");
|
||||
fireEvent.change(nameInput, { target: { value: "test-script" } });
|
||||
|
||||
// Try to save with empty command
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("Script command is required", "error");
|
||||
});
|
||||
});
|
||||
|
||||
it("handles duplicate script name error", async () => {
|
||||
vi.mocked(fetchScripts)
|
||||
.mockResolvedValueOnce({})
|
||||
.mockResolvedValueOnce({});
|
||||
vi.mocked(addScript).mockRejectedValueOnce(new Error("A script with this name already exists"));
|
||||
|
||||
render(
|
||||
<ScriptsModal isOpen={true} onClose={onClose} addToast={addToast} onRunScript={onRunScript} />
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("add-script-btn")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("add-script-btn"));
|
||||
|
||||
fireEvent.change(screen.getByTestId("script-name-input"), { target: { value: "test-script" } });
|
||||
fireEvent.change(screen.getByTestId("script-command-input"), { target: { value: "echo test" } });
|
||||
|
||||
fireEvent.click(screen.getByTestId("script-save-btn"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("A script with this name already exists", "error");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user