Skip to content

Commit b3e1636

Browse files
committed
fix: suppress transient projection markdown updates
1 parent 67dae44 commit b3e1636

4 files changed

Lines changed: 52 additions & 3 deletions

File tree

src/features/editor/plugins/inlineSourceProjection.test.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
22

3+
import { waitFor } from "@testing-library/react";
4+
35
import { runEditorCommand } from "@/features/editor/utils/editorCommands";
4-
import { mountMilkdownEditor, type MountedMilkdownEditor } from "@/test/utils/milkdown";
6+
import {
7+
mountMilkdownEditor,
8+
type MountedMilkdownEditor,
9+
type MountMilkdownEditorOptions,
10+
} from "@/test/utils/milkdown";
511
import {
612
pressKey,
713
setSelectionAtDocumentEnd,
@@ -17,15 +23,21 @@ const mountedEditors: MountedMilkdownEditor[] = [];
1723
const mountEditor = async (
1824
initialMarkdown: string,
1925
onContentTransaction = vi.fn(),
26+
onMarkdownUpdated?: MountMilkdownEditorOptions["onMarkdownUpdated"],
2027
): Promise<MountedMilkdownEditor> => {
2128
const mounted = await mountMilkdownEditor(initialMarkdown, {
2229
onContentTransaction,
30+
onMarkdownUpdated,
2331
rootClassName: "leafdown-editor",
2432
});
2533
mountedEditors.push(mounted);
2634
return mounted;
2735
};
2836

37+
const waitForMarkdownListenerDebounce = async () => {
38+
await new Promise((resolve) => setTimeout(resolve, 300));
39+
};
40+
2941
const enterProjection = (mounted: MountedMilkdownEditor, selector: "em" | "strong") => {
3042
const element = mounted.view.dom.querySelector(selector);
3143

@@ -167,6 +179,30 @@ describe("inline source projection", () => {
167179
expect(hasActiveInlineSourceProjection(mounted.view.state)).toBe(false);
168180
});
169181

182+
it("does not emit transient projected source through markdown updates", async () => {
183+
const onMarkdownUpdated = vi.fn();
184+
const mounted = await mountEditor("**Bold** plain", vi.fn(), onMarkdownUpdated);
185+
186+
enterProjection(mounted, "strong");
187+
await waitForMarkdownListenerDebounce();
188+
189+
expect(onMarkdownUpdated).not.toHaveBeenCalled();
190+
191+
typeText(mounted.view, "er");
192+
await waitForMarkdownListenerDebounce();
193+
194+
expect(onMarkdownUpdated).not.toHaveBeenCalled();
195+
196+
setSelectionAtDocumentEnd(mounted.view);
197+
198+
await waitFor(() => {
199+
expect(onMarkdownUpdated).toHaveBeenCalledWith(
200+
expect.objectContaining({ markdown: "**Bolder** plain\n" }),
201+
);
202+
});
203+
expect(onMarkdownUpdated).toHaveBeenCalledTimes(1);
204+
});
205+
170206
it("uses projection-local undo and redo while projection is active", async () => {
171207
const mounted = await mountEditor("**Bold** plain");
172208

src/features/editor/plugins/inlineSourceProjection.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ export const finalizeInlineSourceProjection = (view: EditorView) => {
113113
export const hasActiveInlineSourceProjection = (state: EditorState) =>
114114
Boolean(getInlineSourceProjectionState(state).session);
115115

116+
export const hasTransientInlineSourceProjection = (state: EditorState) => {
117+
const projectionState = getInlineSourceProjectionState(state);
118+
119+
return Boolean(projectionState.session || projectionState.pendingCommit);
120+
};
121+
116122
export const canUndoInlineSourceProjection = (state: EditorState) => {
117123
const session = getInlineSourceProjectionState(state).session;
118124

src/features/editor/utils/createMilkdownEditor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { createLeafdownImageViewPlugin } from "../plugins/imageView";
1616
import {
1717
createLeafdownInlineSourceProjectionPlugin,
1818
finalizeInlineSourceProjection,
19+
hasTransientInlineSourceProjection,
1920
} from "../plugins/inlineSourceProjection";
2021
import { createLeafdownLinkActivationPlugin } from "../plugins/linkActivation";
2122
import { createLeafdownMarkerPresentationPlugin } from "../plugins/markerPresentation";
@@ -67,7 +68,11 @@ export const createMilkdownEditor = async ({
6768
ctx.set(highlightPluginConfig.key, { parser });
6869

6970
if (onMarkdownUpdated) {
70-
ctx.get(listenerCtx).markdownUpdated((_ctx, markdown, previousMarkdown) => {
71+
ctx.get(listenerCtx).markdownUpdated((listenerCtx, markdown, previousMarkdown) => {
72+
if (hasTransientInlineSourceProjection(listenerCtx.get(editorViewCtx).state)) {
73+
return;
74+
}
75+
7176
onMarkdownUpdated({ markdown, previousMarkdown });
7277
});
7378
}

src/test/utils/milkdown.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
getMilkdownEditorMarkdown,
77
type MilkdownEditorInstance,
88
} from "@/features/editor";
9-
import type { EditorContextPopupRequest } from "@/features/editor";
9+
import type { EditorContextPopupRequest, MilkdownMarkdownUpdate } from "@/features/editor";
1010

1111
export interface MountedMilkdownEditor {
1212
root: HTMLDivElement;
@@ -23,6 +23,7 @@ export interface MountMilkdownEditorOptions {
2323
folderContextPath?: string | null;
2424
onContentTransaction?: () => void;
2525
onCommandStateChanged?: () => void;
26+
onMarkdownUpdated?: (update: MilkdownMarkdownUpdate) => void;
2627
onContextPopupClosed?: () => void;
2728
onContextPopupRequested?: (request: EditorContextPopupRequest) => void;
2829
getContextPopupOpen?: () => boolean;
@@ -41,6 +42,7 @@ export const mountMilkdownEditor = async (
4142
initialMarkdown,
4243
onContentTransaction: options.onContentTransaction,
4344
onCommandStateChanged: options.onCommandStateChanged,
45+
onMarkdownUpdated: options.onMarkdownUpdated,
4446
onContextPopupClosed: options.onContextPopupClosed,
4547
onContextPopupRequested: options.onContextPopupRequested,
4648
getContextPopupOpen: options.getContextPopupOpen,

0 commit comments

Comments
 (0)