Skip to content

Commit 395f7ee

Browse files
committed
block type change tests
1 parent 9fae8f9 commit 395f7ee

3 files changed

Lines changed: 234 additions & 0 deletions

File tree

tests/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# vitest-browser auto-saved debug screenshots on test failure (separate
2+
# from `toMatchScreenshot` reference shots, which use `*-chromium-darwin.png`).
3+
src/browser/**/__screenshots__/**/*-1.png
4+
5+
# vitest-browser attachments (debug artifacts saved during test runs).
6+
.vitest-attachments
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/* eslint-disable testing-library/render-result-naming-convention */
2+
/**
3+
* Vitest browser-mode tests for two-user concurrent type-change
4+
* suggestions. Same shape as `propChanges.concurrent.test.tsx`.
5+
*
6+
* KNOWN BUG: see `typeChanges.test.tsx` – block-type changes in
7+
* suggestion mode currently throw in y-prosemirror's `deltaToPSteps`.
8+
* Both tests below are marked `test.fails`; when the upstream bug is
9+
* fixed they will flip red and we can capture proper snapshots.
10+
*/
11+
import { expect, test } from "vitest";
12+
13+
import { setupConcurrentSuggestionTest } from "./fixtures/concurrentSuggestionFixture.js";
14+
import {
15+
editorHtml,
16+
ydocXml,
17+
} from "./fixtures/suggestionFixture.js";
18+
19+
// Two competing type changes on the same block: A wants a heading, B
20+
// wants a list item.
21+
test.fails(
22+
"concurrent: A → heading, B → list item",
23+
async () => {
24+
const {
25+
userA,
26+
userB,
27+
merged,
28+
baseDoc,
29+
suggestionDocA,
30+
suggestionDocB,
31+
suggestionDocMerged,
32+
screen,
33+
seed,
34+
enableSuggestions,
35+
sync,
36+
} = await setupConcurrentSuggestionTest({
37+
userAAction: "→ heading",
38+
userBAction: "→ list item",
39+
});
40+
41+
userA.editor.replaceBlocks(userA.editor.document, [
42+
{ id: "block-hello", type: "paragraph", content: "hello world" },
43+
]);
44+
seed();
45+
await expect
46+
.element(screen.getByTestId(userA.testId).getByText("hello world"))
47+
.toBeVisible();
48+
49+
enableSuggestions();
50+
51+
const [blockA] = userA.editor.document;
52+
userA.editor.updateBlock(blockA, {
53+
type: "heading",
54+
props: { level: 1 },
55+
});
56+
57+
const [blockB] = userB.editor.document;
58+
userB.editor.updateBlock(blockB, { type: "bulletListItem" });
59+
60+
await expect.poll(() => userA.editor.document[0]?.type).toBe("heading");
61+
await expect
62+
.poll(() => userB.editor.document[0]?.type)
63+
.toBe("bulletListItem");
64+
65+
sync();
66+
67+
await expect(screen.getByTestId("editor-root")).toMatchScreenshot(
68+
"concurrent-heading-vs-list",
69+
);
70+
71+
expect(ydocXml(baseDoc)).toMatchInlineSnapshot();
72+
expect(ydocXml(suggestionDocA)).toMatchInlineSnapshot();
73+
expect(ydocXml(suggestionDocB)).toMatchInlineSnapshot();
74+
expect(ydocXml(suggestionDocMerged)).toMatchInlineSnapshot();
75+
expect(editorHtml(merged.editor)).toMatchInlineSnapshot();
76+
},
77+
);
78+
79+
// Mixed: A does a text edit (no type change), B changes the type.
80+
// Exercises the path where one user's suggestion is a regular text
81+
// diff and the other's is a block-type swap.
82+
test.fails(
83+
"concurrent: A edits text, B → heading",
84+
async () => {
85+
const {
86+
userA,
87+
userB,
88+
merged,
89+
baseDoc,
90+
suggestionDocA,
91+
suggestionDocB,
92+
suggestionDocMerged,
93+
screen,
94+
seed,
95+
enableSuggestions,
96+
sync,
97+
} = await setupConcurrentSuggestionTest({
98+
userAAction: "world → universe",
99+
userBAction: "→ heading",
100+
});
101+
102+
userA.editor.replaceBlocks(userA.editor.document, [
103+
{ id: "block-hello", type: "paragraph", content: "hello world" },
104+
]);
105+
seed();
106+
await expect
107+
.element(screen.getByTestId(userA.testId).getByText("hello world"))
108+
.toBeVisible();
109+
110+
enableSuggestions();
111+
112+
const [blockA] = userA.editor.document;
113+
userA.editor.updateBlock(blockA, {
114+
type: "paragraph",
115+
content: "hello universe",
116+
});
117+
118+
const [blockB] = userB.editor.document;
119+
userB.editor.updateBlock(blockB, {
120+
type: "heading",
121+
props: { level: 1 },
122+
});
123+
124+
await expect
125+
.poll(() =>
126+
userA.editor.prosemirrorState.doc.toString().includes("y-attributed"),
127+
)
128+
.toBe(true);
129+
await expect.poll(() => userB.editor.document[0]?.type).toBe("heading");
130+
131+
sync();
132+
133+
await expect(screen.getByTestId("editor-root")).toMatchScreenshot(
134+
"concurrent-text-edit-vs-heading",
135+
);
136+
137+
expect(ydocXml(baseDoc)).toMatchInlineSnapshot();
138+
expect(ydocXml(suggestionDocA)).toMatchInlineSnapshot();
139+
expect(ydocXml(suggestionDocB)).toMatchInlineSnapshot();
140+
expect(ydocXml(suggestionDocMerged)).toMatchInlineSnapshot();
141+
expect(editorHtml(merged.editor)).toMatchInlineSnapshot();
142+
},
143+
);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-disable testing-library/render-result-naming-convention */
2+
/**
3+
* Vitest browser-mode tests for type-change suggestions: swapping the
4+
* block type (paragraph ↔ heading ↔ list item) while preserving its
5+
* inline content. Same shape as `propChanges.test.tsx`.
6+
*
7+
* KNOWN BUG: `editor.updateBlock(block, { type: ... })` in suggestion
8+
* mode currently throws `TransformError: No node at mark step's
9+
* position` from y-prosemirror's `deltaToPSteps`. Tests are marked
10+
* `test.fails` so they pass while the bug exists – when the
11+
* underlying issue is fixed, the tests will start passing for real
12+
* and `test.fails` will flip them red, signalling that snapshots need
13+
* to be captured.
14+
*/
15+
import { SuggestionsExtension } from "@blocknote/core/y";
16+
import { expect, test } from "vitest";
17+
18+
import {
19+
editorHtml,
20+
setupSuggestionTest,
21+
ydocXml,
22+
} from "./fixtures/suggestionFixture.js";
23+
24+
// Demote a bullet-list item to a plain paragraph. Inline content
25+
// "hello world" stays the same; only the wrapping node type changes.
26+
test.fails("suggestion mode: change list item to paragraph", async () => {
27+
const { editor, screen, baseDoc, suggestionDoc, sync } =
28+
await setupSuggestionTest({ userAction: "list → paragraph" });
29+
30+
editor.replaceBlocks(editor.document, [
31+
{
32+
id: "block-hello",
33+
type: "bulletListItem",
34+
content: "hello world",
35+
},
36+
]);
37+
sync();
38+
await expect
39+
.element(screen.getByTestId("editor-A").getByText("hello world"))
40+
.toBeVisible();
41+
42+
editor.getExtension(SuggestionsExtension)!.enableSuggestions();
43+
44+
const [block] = editor.document;
45+
editor.updateBlock(block, { type: "paragraph" });
46+
47+
await expect.poll(() => editor.document[0]?.type).toBe("paragraph");
48+
49+
await expect(screen.getByTestId("editor-root")).toMatchScreenshot(
50+
"type-change-list-to-paragraph",
51+
);
52+
53+
expect(ydocXml(baseDoc)).toMatchInlineSnapshot();
54+
expect(ydocXml(suggestionDoc)).toMatchInlineSnapshot();
55+
expect(editorHtml(editor)).toMatchInlineSnapshot();
56+
});
57+
58+
// Promote a paragraph to a level-1 heading. Same inline content.
59+
test.fails("suggestion mode: change paragraph to heading", async () => {
60+
const { editor, screen, baseDoc, suggestionDoc, sync } =
61+
await setupSuggestionTest({ userAction: "paragraph → heading" });
62+
63+
editor.replaceBlocks(editor.document, [
64+
{ id: "block-hello", type: "paragraph", content: "hello world" },
65+
]);
66+
sync();
67+
await expect
68+
.element(screen.getByTestId("editor-A").getByText("hello world"))
69+
.toBeVisible();
70+
71+
editor.getExtension(SuggestionsExtension)!.enableSuggestions();
72+
73+
const [block] = editor.document;
74+
editor.updateBlock(block, { type: "heading", props: { level: 1 } });
75+
76+
await expect.poll(() => editor.document[0]?.type).toBe("heading");
77+
78+
await expect(screen.getByTestId("editor-root")).toMatchScreenshot(
79+
"type-change-paragraph-to-heading",
80+
);
81+
82+
expect(ydocXml(baseDoc)).toMatchInlineSnapshot();
83+
expect(ydocXml(suggestionDoc)).toMatchInlineSnapshot();
84+
expect(editorHtml(editor)).toMatchInlineSnapshot();
85+
});

0 commit comments

Comments
 (0)