import "../../index.css"; import type { LineAnnotation, SelectedLineRange } from "@pierre/diffs"; import { Editor } from "@pierre/diffs/editor"; import { EditorProvider, File } from "@pierre/diffs/react"; import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import { page } from "vite-plus/test/browser"; import { render } from "vitest-browser-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { installFileEditorDismissal } from "./fileEditorDismissal"; interface AnnotationMetadata { label: string; } function dispatchPointer(target: EventTarget, type: string, pointerId: number): void { target.dispatchEvent( new PointerEvent(type, { bubbles: true, cancelable: true, composed: true, pointerId, pointerType: "mouse", }), ); } function EditableAnnotatedFile() { const [selectedLines, setSelectedLines] = useState(null); const [lineAnnotations, setLineAnnotations] = useState[]>([]); const rootRef = useRef(null); const editor = useMemo(() => new Editor(), []); useEffect(() => () => editor.cleanUp(), [editor]); useEffect(() => { const root = rootRef.current; if (!root) return; return installFileEditorDismissal({ root, editor, isBlocked: () => false, onDismiss: () => setSelectedLines(null), }); }, [editor]); return ( <>
file={{ name: "example.ts", contents: "one\ntwo\nthree\n" }} options={{ disableFileHeader: true, enableGutterUtility: true, enableLineSelection: true, onGutterUtilityClick: setSelectedLines, onLineSelectionChange: setSelectedLines, onLineSelectionEnd: (range) => { setSelectedLines(range); if (range) { setLineAnnotations([ { lineNumber: Math.max(range.start, range.end), metadata: { label: `${range.start}:${range.end}` }, }, ]); } }, }} selectedLines={selectedLines} lineAnnotations={lineAnnotations} renderAnnotation={(annotation) => (
{annotation.metadata.label}
)} disableWorkerPool contentEditable />
); } async function getEditableFile() { const file = await vi.waitFor(() => { const element = document.querySelector("diffs-container"); expect(element?.shadowRoot).not.toBeNull(); return element!; }); const content = await vi.waitFor(() => { const element = file?.shadowRoot?.querySelector("[data-content]") ?? null; expect(element).not.toBeNull(); return element!; }); return { file, content }; } describe("editable Pierre file annotations", () => { afterEach(() => { document.body.innerHTML = ""; }); it("keeps gutter selection and annotations enabled while the file is editable", async () => { const screen = await render(); try { const { file, content } = await getEditableFile(); const secondLineNumber = await vi.waitFor(() => { const element = file?.shadowRoot?.querySelector('[data-column-number="2"]') ?? null; expect(element).not.toBeNull(); return element; }); await vi.waitFor(() => { expect( file?.shadowRoot?.querySelector("pre")?.hasAttribute("data-interactive-line-numbers"), ).toBe(true); }); dispatchPointer(secondLineNumber!, "pointerdown", 1); dispatchPointer(secondLineNumber!, "pointerup", 1); await vi.waitFor(() => { expect(document.querySelector("[data-test-file-annotation]")?.textContent).toBe("2:2"); }); expect(content.contentEditable).toBe("true"); expect(content.getAttribute("role")).toBe("textbox"); } finally { await screen.unmount(); } }); it("dismisses editor focus and selection with outside click or Escape", async () => { const screen = await render(); try { const { file, content } = await getEditableFile(); content.focus(); expect(file?.shadowRoot?.activeElement).toBe(content); await page.getByRole("button", { name: "Outside file" }).click(); await vi.waitFor(() => { expect(file?.shadowRoot?.activeElement).not.toBe(content); }); content.focus(); expect(file?.shadowRoot?.activeElement).toBe(content); content.dispatchEvent( new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true, composed: true, }), ); await vi.waitFor(() => { expect(file?.shadowRoot?.activeElement).not.toBe(content); }); } finally { await screen.unmount(); } }); });