/* Copyright 2024 Marimo. All rights reserved. */
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TooltipProvider } from "@/components/ui/tooltip";
import type { CellId } from "@/core/cells/ids";
import * as requestsModule from "@/core/network/requests";
import * as copyModule from "@/utils/copy";
import { IslandControls } from "../IslandControls";
// Mock the dependencies
vi.mock("@/core/network/requests", () => ({
useRequestClient: vi.fn(),
}));
vi.mock("@/utils/copy", () => ({
copyToClipboard: vi.fn(),
}));
describe("IslandControls", () => {
const mockSendRun = vi.fn();
const mockCopyToClipboard = vi.fn();
const mockCodeCallback = vi.fn(() => "print('test code')");
const cellId = "test-cell-id" as CellId;
// Helper to render with required providers
const renderWithProviders = (component: React.ReactElement) => {
return render({component});
};
beforeEach(() => {
vi.clearAllMocks();
mockSendRun.mockResolvedValue(undefined);
vi.spyOn(requestsModule, "useRequestClient").mockReturnValue({
sendRun: mockSendRun,
} as any);
vi.spyOn(copyModule, "copyToClipboard").mockImplementation(
mockCopyToClipboard,
);
});
it("should not display when visible is false", () => {
const { container } = renderWithProviders(
,
);
const controlsDiv = container.firstChild as HTMLElement;
expect(controlsDiv).toBeDefined();
expect(controlsDiv.style.display).toBe("none");
});
it("should display when visible is true", () => {
const { container } = renderWithProviders(
,
);
const controlsDiv = container.firstChild as HTMLElement;
expect(controlsDiv.style.display).toBe("flex");
});
it("should render copy and run buttons", () => {
renderWithProviders(
,
);
// Should have 2 buttons (copy and run)
const buttons = screen.getAllByRole("button");
expect(buttons).toHaveLength(2);
});
it("should copy code to clipboard when copy button is clicked", async () => {
renderWithProviders(
,
);
const buttons = screen.getAllByRole("button");
const copyButton = buttons[0]; // First button is copy
fireEvent.click(copyButton);
expect(mockCodeCallback).toHaveBeenCalled();
expect(mockCopyToClipboard).toHaveBeenCalledWith("print('test code')");
});
it("should run cell when run button is clicked", async () => {
renderWithProviders(
,
);
const buttons = screen.getAllByRole("button");
const runButton = buttons[1]; // Second button is run
fireEvent.click(runButton);
// Wait for async operation
await vi.waitFor(() => {
expect(mockCodeCallback).toHaveBeenCalled();
expect(mockSendRun).toHaveBeenCalledWith({
cellIds: [cellId],
codes: ["print('test code')"],
});
});
});
it("should handle run errors gracefully", async () => {
const consoleErrorSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});
mockSendRun.mockRejectedValueOnce(new Error("Run failed"));
renderWithProviders(
,
);
const buttons = screen.getAllByRole("button");
const runButton = buttons[1];
fireEvent.click(runButton);
// Wait for error to be logged
await vi.waitFor(() => {
expect(consoleErrorSpy).toHaveBeenCalled();
});
consoleErrorSpy.mockRestore();
});
it("should get fresh code on each button click", async () => {
let callCount = 0;
const dynamicCodeCallback = vi.fn(() => `code version ${++callCount}`);
renderWithProviders(
,
);
const buttons = screen.getAllByRole("button");
const copyButton = buttons[0];
const runButton = buttons[1];
fireEvent.click(copyButton);
expect(mockCopyToClipboard).toHaveBeenCalledWith("code version 1");
fireEvent.click(runButton);
await vi.waitFor(() => {
expect(mockSendRun).toHaveBeenCalledWith({
cellIds: [cellId],
codes: ["code version 2"],
});
});
fireEvent.click(copyButton);
expect(mockCopyToClipboard).toHaveBeenCalledWith("code version 3");
});
});