import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { HttpResponse, http } from "msw"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { mockMasterKey } from "../../test/fixtures/auth.fixture"; import { mockUser } from "../../test/fixtures/user.fixture"; import { server } from "../../test/mocks/server"; import { endpoints } from "../config/endpoints"; import { useAuthStore } from "../store/useAuthStore"; import { useKeyStore } from "../store/useKeyStore"; import Editor from "./Editor"; const API_URL = import.meta.env.VITE_API_URL; // Mock ComposeCanvas to avoid Fabric.js issues and check readOnly prop vi.mock("../components/editor/ComposeCanvas", () => ({ ComposeCanvas: vi.fn(({ readOnly }) => (
Canvas
)), })); // Mock CryptoUtils to avoid real crypto calls in UI tests vi.mock("../utils/crypto", () => { return { CryptoUtils: class { initialize = vi.fn().mockResolvedValue(undefined); encryptLetter = vi.fn().mockResolvedValue({ encrypted_content: "enc-content", encrypted_dek: "enc-dek", sharingKey: "share-key", }); encryptMetadata = vi.fn().mockResolvedValue({ encrypted_content: "enc-meta", encrypted_dek: "enc-dek", }); decryptMetadata = vi.fn().mockResolvedValue({ recipient: "Test User" }); decryptLetter = vi.fn().mockResolvedValue("{}"); extractSharingKey = vi.fn().mockResolvedValue("share-key"); }, }; }); describe("Editor Page", () => { beforeEach(() => { vi.clearAllMocks(); useAuthStore.setState({ user: mockUser, accessToken: "fake-token", isInitializing: false, }); useKeyStore.setState({ masterKey: mockMasterKey }); }); it("should set canvas to readOnly when status is VAULT", async () => { server.use( http.get(`${API_URL}${endpoints.LETTERS}:id/`, () => { return HttpResponse.json({ public_id: "test-id", status: "DRAFT", updated_at: new Date().toISOString(), encrypted_content: "{}", encrypted_metadata: "{}", encrypted_dek: "wrapped-dek", }); }), http.put(`${API_URL}${endpoints.LETTERS}:id/`, () => { return HttpResponse.json({ status: "success" }); }), ); const { container } = render( } /> , ); // Wait for initial load to complete await waitFor(() => { expect(screen.queryByText(/Opening your draft/i)).not.toBeInTheDocument(); }); const canvas = screen.getByTestId("canvas"); expect(canvas.getAttribute("data-readonly")).toBe("false"); const toolbar = container.querySelector("#writer-toolbar"); const sealBtn = toolbar?.querySelector(".btn-primary"); if (!sealBtn) throw new Error("Seal button not found"); fireEvent.click(sealBtn); // Click Vault to show confirm modal const vaultBtn = screen.getByRole("button", { name: /vault/i }); fireEvent.click(vaultBtn); // Set date and submit vault form const dateInput = container.querySelector('input[name="vault-date"]'); if (!dateInput) throw new Error("Date input not found"); fireEvent.change(dateInput, { target: { value: "2026-12-31" } }); const confirmVaultBtn = container.querySelector( 'button[form="vault-form"]', ); if (!confirmVaultBtn) throw new Error("Confirm vault button not found"); fireEvent.click(confirmVaultBtn); // Wait for save to complete and check readOnly await waitFor(() => { expect(screen.getByText(/Your letter is saved/i)).toBeInTheDocument(); }); expect(canvas.getAttribute("data-readonly")).toBe("true"); expect(screen.getByLabelText(/recipient/i)).toBeDisabled(); }); it("should set canvas to readOnly when status is SEALED", async () => { server.use( http.get(`${API_URL}${endpoints.LETTERS}:id/`, () => { return HttpResponse.json({ public_id: "test-id", status: "DRAFT", updated_at: new Date().toISOString(), encrypted_content: "{}", encrypted_metadata: "{}", encrypted_dek: "wrapped-dek", }); }), http.put(`${API_URL}${endpoints.LETTERS}:id/`, () => { return HttpResponse.json({ status: "success" }); }), ); const { container } = render( } /> , ); await waitFor(() => { expect(screen.queryByText(/Opening your draft/i)).not.toBeInTheDocument(); }); const canvas = screen.getByTestId("canvas"); const toolbar = container.querySelector("#writer-toolbar"); const sealBtn = toolbar?.querySelector(".btn-primary"); if (!sealBtn) throw new Error("Seal button not found"); fireEvent.click(sealBtn); // The secondary seal button appears (it has btn-accent class) const secondarySealBtn = container.querySelector(".btn-accent"); if (!secondarySealBtn) throw new Error("Secondary seal button not found"); fireEvent.click(secondarySealBtn); await waitFor(() => { expect(screen.getByText(/Your letter is saved/i)).toBeInTheDocument(); }); expect(canvas.getAttribute("data-readonly")).toBe("true"); expect(screen.getByLabelText(/recipient/i)).toBeDisabled(); }); });