/* Copyright 2026 Marimo. All rights reserved. */
import { act, renderHook } from "@testing-library/react";
import { createStore, Provider } from "jotai";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MockNotebook } from "@/__mocks__/notebook";
import { notebookAtom } from "@/core/cells/cells";
import { CellId } from "@/core/cells/ids";
import { type AppMode, viewStateAtom } from "@/core/mode";
import { useExpandedConsoleOutput, useExpandedOutput } from "../outputs";
const saveCellConfig = vi.fn().mockResolvedValue(null);
vi.mock("@/core/network/requests", () => ({
getRequestClient: () => ({ saveCellConfig }),
}));
function setup(opts: { mode: AppMode; expandOutput?: boolean }) {
const cellId = CellId.create();
const store = createStore();
store.set(
notebookAtom,
MockNotebook.notebookState({
cellData: {
[cellId]: { config: { expand_output: opts.expandOutput ?? false } },
},
}),
);
store.set(viewStateAtom, { mode: opts.mode, cellAnchor: null });
const { result } = renderHook(() => useExpandedOutput(cellId), {
wrapper: ({ children }) => {children},
});
return { cellId, store, result };
}
const configOf = (store: ReturnType, cellId: CellId) =>
store.get(notebookAtom).cellData[cellId].config;
describe("useExpandedOutput", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("reads the initial state from the cell's expand_output config", () => {
expect(setup({ mode: "edit" }).result.current[0]).toBe(false);
expect(setup({ mode: "edit", expandOutput: true }).result.current[0]).toBe(
true,
);
});
it("persists the toggle to the cell's config in edit mode", () => {
const { cellId, store, result } = setup({ mode: "edit" });
act(() => result.current[1](true));
expect(result.current[0]).toBe(true);
expect(configOf(store, cellId).expand_output).toBe(true);
expect(saveCellConfig).toHaveBeenCalledWith({
configs: { [cellId]: { expand_output: true } },
});
});
it("collapses by clearing the config", () => {
const { cellId, store, result } = setup({
mode: "edit",
expandOutput: true,
});
act(() => result.current[1](false));
expect(result.current[0]).toBe(false);
expect(configOf(store, cellId).expand_output).toBe(false);
expect(saveCellConfig).toHaveBeenCalledWith({
configs: { [cellId]: { expand_output: false } },
});
});
it("keeps the toggle in memory outside of edit mode", () => {
const { cellId, store, result } = setup({ mode: "read" });
act(() => result.current[1](true));
expect(result.current[0]).toBe(true);
// Cell configs are read-only outside of edit mode.
expect(configOf(store, cellId).expand_output).toBe(false);
expect(saveCellConfig).not.toHaveBeenCalled();
});
it("still honors expand_output outside of edit mode", () => {
const { result } = setup({ mode: "read", expandOutput: true });
expect(result.current[0]).toBe(true);
});
// `expand_output` covers the cell's output area only. Console output is
// clamped independently and stays an in-memory, per-session toggle.
it("does not touch the config when console output is expanded", () => {
const cellId = CellId.create();
const store = createStore();
store.set(
notebookAtom,
MockNotebook.notebookState({ cellData: { [cellId]: {} } }),
);
store.set(viewStateAtom, { mode: "edit", cellAnchor: null });
const { result } = renderHook(() => useExpandedConsoleOutput(cellId), {
wrapper: ({ children }) => {children},
});
act(() => result.current[1](true));
expect(result.current[0]).toBe(true);
expect(configOf(store, cellId).expand_output).toBe(false);
expect(saveCellConfig).not.toHaveBeenCalled();
});
});