73 lines
3.2 KiB
TypeScript
73 lines
3.2 KiB
TypeScript
import { EditorState } from "@codemirror/state";
|
|
import { EditorView } from "@codemirror/view";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { createController } from "@/components/MemoEditor/Editor/controller";
|
|
import { type UploadAnchorDescriptor, uploadAnchorField } from "@/components/MemoEditor/Editor/uploadAnchors";
|
|
|
|
const descriptor = (id: string): UploadAnchorDescriptor => ({
|
|
id,
|
|
status: "uploading",
|
|
completed: 0,
|
|
total: 1,
|
|
message: "Uploading images 0/1",
|
|
retryLabel: "Retry",
|
|
keepLabel: "Keep as attachments",
|
|
onRetry: vi.fn(),
|
|
onKeepAttachments: vi.fn(),
|
|
});
|
|
|
|
const view = (doc = "") => new EditorView({ state: EditorState.create({ doc, extensions: [uploadAnchorField] }) });
|
|
|
|
describe("editor upload anchors", () => {
|
|
it("maps the anchor through edits before resolving Markdown", () => {
|
|
const editorView = view("alpha");
|
|
const controller = createController(editorView, {} as never);
|
|
controller.setCursor(5);
|
|
controller.createUploadAnchor(descriptor("upload-one"));
|
|
|
|
editorView.dispatch({ changes: { from: 0, insert: "X" } });
|
|
controller.resolveUploadAnchor("upload-one", "");
|
|
|
|
expect(controller.getMarkdown()).toBe("Xalpha\n\n");
|
|
expect(editorView.dom.querySelector(".cm-upload-anchor")).toBeNull();
|
|
});
|
|
|
|
it("cancels an anchor without changing serialized Markdown", () => {
|
|
const editorView = view("alpha");
|
|
const controller = createController(editorView, {} as never);
|
|
controller.createUploadAnchor(descriptor("upload-two"), 2);
|
|
expect(editorView.dom.querySelector(".cm-upload-anchor")).not.toBeNull();
|
|
|
|
controller.cancelUploadAnchor("upload-two");
|
|
|
|
expect(controller.getMarkdown()).toBe("alpha");
|
|
expect(editorView.dom.querySelector(".cm-upload-anchor")).toBeNull();
|
|
});
|
|
|
|
// The buttons that reach insertMarkdown live outside the editor, and CodeMirror
|
|
// keeps its selection across blur, so an insert must not consume the range.
|
|
it("inserts at the caret without replacing the selection", () => {
|
|
const editorView = view("keep this paragraph of text");
|
|
const controller = createController(editorView, {} as never);
|
|
editorView.dispatch({ selection: { anchor: 5, head: 19 } });
|
|
|
|
controller.insertMarkdown("");
|
|
|
|
expect(controller.getMarkdown()).toBe("keep this paragraph\n\n\n\n of text");
|
|
});
|
|
|
|
it("re-renders the anchor when only its labels or callbacks change", () => {
|
|
const editorView = view("alpha");
|
|
const controller = createController(editorView, {} as never);
|
|
const stale = vi.fn();
|
|
controller.createUploadAnchor({ ...descriptor("upload-three"), status: "failed", onRetry: stale });
|
|
|
|
const fresh = vi.fn();
|
|
controller.updateUploadAnchor({ ...descriptor("upload-three"), status: "failed", message: "Retrying soon", onRetry: fresh });
|
|
|
|
expect(editorView.dom.querySelector(".cm-upload-anchor-badge")?.textContent).toBe("Retrying soon");
|
|
editorView.dom.querySelector<HTMLButtonElement>(".cm-upload-anchor-action")?.click();
|
|
expect(fresh).toHaveBeenCalledTimes(1);
|
|
expect(stale).not.toHaveBeenCalled();
|
|
});
|
|
});
|