@surrealdb/ui 1.2.12 → 1.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.claude/launch.json +17 -0
  2. package/.oxfmtrc.json +27 -0
  3. package/.oxlintrc.json +23 -0
  4. package/.zed/settings.json +34 -34
  5. package/AGENTS.md +25 -19
  6. package/CLAUDE.md +111 -0
  7. package/README.md +15 -10
  8. package/dist/icons.d.ts +4 -0
  9. package/dist/icons.js +91 -89
  10. package/dist/icons.js.map +1 -1
  11. package/dist/ui.css +1 -1
  12. package/dist/ui.d.ts +259 -1
  13. package/dist/ui.js +9807 -12265
  14. package/dist/ui.js.map +1 -1
  15. package/doc/code-style.md +264 -0
  16. package/doc/release.md +102 -0
  17. package/doc/stacker-plan.md +219 -0
  18. package/package.json +120 -116
  19. package/release-notes.md +0 -0
  20. package/res/_mixins.scss +15 -11
  21. package/tests/_setup/e2e-helpers.tsx +85 -85
  22. package/tests/_setup/portable-stories.ts +11 -10
  23. package/tests/e2e/MarkdownEditor/content-blocks.test.tsx +140 -139
  24. package/tests/e2e/MarkdownEditor/edits.test.tsx +101 -100
  25. package/tests/e2e/MarkdownEditor/heading-fold.test.tsx +35 -34
  26. package/tests/e2e/MarkdownEditor/hybrid-widgets.test.tsx +178 -177
  27. package/tests/e2e/MarkdownEditor/image-on-image.test.tsx +50 -49
  28. package/tests/e2e/MarkdownEditor/jsx-block-content.test.tsx +195 -194
  29. package/tests/e2e/MarkdownEditor/jsx-highlight.test.tsx +47 -46
  30. package/tests/e2e/MarkdownEditor/jsx-inline-badges.test.tsx +35 -34
  31. package/tests/e2e/MarkdownEditor/jsx-inline-click.test.tsx +39 -38
  32. package/tests/e2e/MarkdownEditor/jsx-selection.test.tsx +28 -27
  33. package/tests/e2e/MarkdownEditor/link-placeholder.test.tsx +49 -48
  34. package/tests/e2e/MarkdownEditor/media-align.test.tsx +40 -39
  35. package/tests/e2e/MarkdownEditor/media-edit.test.tsx +49 -48
  36. package/tests/e2e/MarkdownEditor/media-sizing.test.tsx +108 -107
  37. package/tests/e2e/MarkdownEditor/modes.test.tsx +79 -78
  38. package/tests/e2e/MarkdownEditor/onimage-parity.test.tsx +29 -28
  39. package/tests/e2e/MarkdownEditor/regressions.test.tsx +194 -193
  40. package/tests/e2e/MarkdownEditor/slash-commands.test.tsx +81 -80
  41. package/tests/e2e/MarkdownEditor/table-click.test.tsx +36 -35
  42. package/tests/e2e/MarkdownEditor/table-controls.test.tsx +44 -43
  43. package/tests/e2e/MarkdownEditor/table-format.test.tsx +28 -27
  44. package/tests/e2e/MarkdownEditor/undo-redo.test.tsx +22 -21
  45. package/tests/e2e/MarkdownViewer/parity.test.tsx +144 -143
  46. package/tests/e2e/MarkdownViewer/render.test.tsx +20 -19
  47. package/tests/e2e/Stacker/stacker.test.tsx +304 -0
  48. package/tests/unit/Editor/helpers.test.ts +26 -26
  49. package/tests/unit/MarkdownConformance/error-isolation.test.tsx +26 -25
  50. package/tests/unit/MarkdownConformance/indentation.test.tsx +20 -19
  51. package/tests/unit/MarkdownConformance/inline-code-fuzz.test.tsx +255 -0
  52. package/tests/unit/MarkdownConformance/jsx-attributes.test.tsx +39 -38
  53. package/tests/unit/MarkdownConformance/jsx-fuzz.test.tsx +122 -122
  54. package/tests/unit/MarkdownConformance/links.test.tsx +78 -54
  55. package/tests/unit/MarkdownConformance/render-helpers.tsx +60 -60
  56. package/tests/unit/MarkdownEditor/code-info.test.ts +58 -58
  57. package/tests/unit/MarkdownEditor/decorations.test.ts +423 -421
  58. package/tests/unit/MarkdownEditor/editor-ready.test.ts +22 -22
  59. package/tests/unit/MarkdownEditor/html-descriptors.test.ts +70 -70
  60. package/tests/unit/MarkdownEditor/indented-fence.test.ts +80 -80
  61. package/tests/unit/MarkdownEditor/jsx-attr-scan.test.ts +87 -87
  62. package/tests/unit/MarkdownEditor/jsx-block-widget.test.ts +51 -51
  63. package/tests/unit/MarkdownEditor/jsx-tag-grammar.test.ts +63 -63
  64. package/tests/unit/MarkdownEditor/list-indent.test.ts +67 -67
  65. package/tests/unit/MarkdownEditor/slash-commands.test.ts +150 -150
  66. package/tests/unit/MarkdownEditor/table-format.test.ts +67 -67
  67. package/tests/unit/MarkdownEditor/table.test.ts +96 -96
  68. package/tests/unit/MarkdownEditor/triggers.test.ts +167 -167
  69. package/tests/unit/MarkdownEditor/widget-store.test.ts +90 -90
  70. package/tests/unit/MarkdownViewer/callout.test.tsx +42 -42
  71. package/tests/unit/MarkdownViewer/code-highlight.test.tsx +41 -41
  72. package/tests/unit/MarkdownViewer/code-title.test.tsx +45 -45
  73. package/tests/unit/MarkdownViewer/features.test.tsx +129 -129
  74. package/tests/unit/MarkdownViewer/headings.test.tsx +28 -28
  75. package/tests/unit/MarkdownViewer/indented-jsx-children.test.tsx +53 -53
  76. package/tests/unit/MarkdownViewer/jsx-block-nesting.test.tsx +137 -137
  77. package/tests/unit/MarkdownViewer/jsx.test.tsx +188 -188
  78. package/tests/unit/MarkdownViewer/list-bullets.test.tsx +31 -31
  79. package/tests/unit/MarkdownViewer/list-code.test.tsx +45 -45
  80. package/tests/unit/MarkdownViewer/preserve-newlines.test.tsx +109 -109
  81. package/tests/unit/MarkdownViewer/read-time.test.ts +13 -13
  82. package/tests/unit/MarkdownViewer/renderers.test.tsx +75 -75
  83. package/tests/unit/MarkdownViewer/runnable.test.tsx +51 -51
  84. package/tests/unit/MarkdownViewer/ssr.test.tsx +56 -55
  85. package/tests/unit/MarkdownViewer/syntax-highlighting.test.tsx +43 -42
  86. package/tests/unit/MarkdownViewer/tabs-fenced-code.test.tsx +120 -120
  87. package/tests/unit/MarkdownViewer/tabs.test.tsx +83 -82
  88. package/tests/unit/Stacker/drag.test.ts +21 -0
  89. package/tests/unit/Stacker/helpers.test.ts +105 -0
  90. package/tests/unit/Stacker/reducer.test.ts +107 -0
  91. package/tools/duplicate-vector-mappings.json +141 -141
  92. package/tools/glass-export-overrides.json +6 -6
  93. package/tools/icon-parser.ts +41 -40
  94. package/tools/integration-gradient-exports.json +55 -55
  95. package/tools/picto-compare.ts +32 -32
  96. package/tools/picto-merge-urls.ts +19 -19
  97. package/tools/picto-sync.ts +543 -543
  98. package/tools/picto-verify-gradients.ts +50 -50
  99. package/tools/vector-export-nodes.json +975 -975
@@ -2,46 +2,47 @@ import { EditorSelection } from "@codemirror/state";
2
2
  import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
3
  import { composeStories } from "@storybook/react-vite";
4
4
  import { describe, expect, it } from "vitest";
5
+
5
6
  import {
6
- mountStory,
7
- nextFrame,
8
- page,
9
- preparePreviewEditor,
10
- scrollEditorTo,
7
+ mountStory,
8
+ nextFrame,
9
+ page,
10
+ preparePreviewEditor,
11
+ scrollEditorTo,
11
12
  } from "../../_setup/e2e-helpers";
12
13
  import { md } from "../../_setup/markdown-classes";
13
14
 
14
15
  const Stories = composeStories(MarkdownStories);
15
16
 
16
17
  describe("MarkdownEditor / table cell click", () => {
17
- it("places the caret in the source cell when a rendered cell is clicked", async () => {
18
- mountStory(<Stories.Editor />);
19
- const view = await preparePreviewEditor();
20
- const doc = view.state.doc.toString();
21
-
22
- const tablePos = doc.indexOf("| Feature |");
23
- expect(tablePos).toBeGreaterThan(-1);
24
- await scrollEditorTo(view, tablePos);
25
-
26
- // Park the caret far away so the table renders as a widget.
27
- view.dispatch({ selection: EditorSelection.cursor(0) });
28
- await nextFrame();
29
- await nextFrame();
30
-
31
- const widget = document.querySelector(`.${md.tableWidget}`);
32
- expect(widget).not.toBeNull();
33
-
34
- // Click the "Headers" body cell.
35
- const cell = page.getByText("Headers", { exact: true });
36
- await cell.click();
37
- await nextFrame();
38
-
39
- // The caret should land on the "Headers" cell inside the table source.
40
- const expected = doc.indexOf("Headers", tablePos);
41
- expect(view.state.selection.main.head).toBe(expected);
42
-
43
- // Clicking activates the block, revealing the raw source (widget gone).
44
- await nextFrame();
45
- expect(document.querySelector(`.${md.tableWidget}`)).toBeNull();
46
- });
18
+ it("places the caret in the source cell when a rendered cell is clicked", async () => {
19
+ mountStory(<Stories.Editor />);
20
+ const view = await preparePreviewEditor();
21
+ const doc = view.state.doc.toString();
22
+
23
+ const tablePos = doc.indexOf("| Feature |");
24
+ expect(tablePos).toBeGreaterThan(-1);
25
+ await scrollEditorTo(view, tablePos);
26
+
27
+ // Park the caret far away so the table renders as a widget.
28
+ view.dispatch({ selection: EditorSelection.cursor(0) });
29
+ await nextFrame();
30
+ await nextFrame();
31
+
32
+ const widget = document.querySelector(`.${md.tableWidget}`);
33
+ expect(widget).not.toBeNull();
34
+
35
+ // Click the "Headers" body cell.
36
+ const cell = page.getByText("Headers", { exact: true });
37
+ await cell.click();
38
+ await nextFrame();
39
+
40
+ // The caret should land on the "Headers" cell inside the table source.
41
+ const expected = doc.indexOf("Headers", tablePos);
42
+ expect(view.state.selection.main.head).toBe(expected);
43
+
44
+ // Clicking activates the block, revealing the raw source (widget gone).
45
+ await nextFrame();
46
+ expect(document.querySelector(`.${md.tableWidget}`)).toBeNull();
47
+ });
47
48
  });
@@ -2,55 +2,56 @@ import { EditorSelection } from "@codemirror/state";
2
2
  import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
3
  import { composeStories } from "@storybook/react-vite";
4
4
  import { describe, expect, it } from "vitest";
5
+
5
6
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
6
7
 
7
8
  const Stories = composeStories(MarkdownStories);
8
9
 
9
10
  function clickControl(label: string): boolean {
10
- const button = document.querySelector<HTMLButtonElement>(`button[aria-label="${label}"]`);
11
- if (!button) return false;
12
- button.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
13
- button.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
14
- return true;
11
+ const button = document.querySelector<HTMLButtonElement>(`button[aria-label="${label}"]`);
12
+ if (!button) return false;
13
+ button.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
14
+ button.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
15
+ return true;
15
16
  }
16
17
 
17
18
  describe("MarkdownEditor / table controls", () => {
18
- it("adds and removes rows and columns from the active table", async () => {
19
- mountStory(<Stories.EditorTableFormatter />);
20
- const view = await getEditorView();
21
- await nextFrame();
22
-
23
- // Park the caret inside the table to reveal the floating controls.
24
- const adaPos = view.state.doc.toString().indexOf("Ada");
25
- view.dispatch({ selection: EditorSelection.cursor(adaPos) });
26
- view.focus();
27
- await nextFrame();
28
- await nextFrame();
29
-
30
- const tableLines = () =>
31
- view.state.doc
32
- .toString()
33
- .split("\n")
34
- .filter((l) => l.includes("|"));
35
- const headerPipes = () => (tableLines()[0]?.match(/\|/g) ?? []).length;
36
-
37
- const beforeCols = headerPipes();
38
- const beforeRows = tableLines().length;
39
-
40
- expect(clickControl("Add column")).toBe(true);
41
- await nextFrame();
42
- expect(headerPipes()).toBe(beforeCols + 1);
43
-
44
- expect(clickControl("Add row")).toBe(true);
45
- await nextFrame();
46
- expect(tableLines().length).toBe(beforeRows + 1);
47
-
48
- expect(clickControl("Remove row")).toBe(true);
49
- await nextFrame();
50
- expect(tableLines().length).toBe(beforeRows);
51
-
52
- expect(clickControl("Remove column")).toBe(true);
53
- await nextFrame();
54
- expect(headerPipes()).toBe(beforeCols);
55
- });
19
+ it("adds and removes rows and columns from the active table", async () => {
20
+ mountStory(<Stories.EditorTableFormatter />);
21
+ const view = await getEditorView();
22
+ await nextFrame();
23
+
24
+ // Park the caret inside the table to reveal the floating controls.
25
+ const adaPos = view.state.doc.toString().indexOf("Ada");
26
+ view.dispatch({ selection: EditorSelection.cursor(adaPos) });
27
+ view.focus();
28
+ await nextFrame();
29
+ await nextFrame();
30
+
31
+ const tableLines = () =>
32
+ view.state.doc
33
+ .toString()
34
+ .split("\n")
35
+ .filter((l) => l.includes("|"));
36
+ const headerPipes = () => (tableLines()[0]?.match(/\|/g) ?? []).length;
37
+
38
+ const beforeCols = headerPipes();
39
+ const beforeRows = tableLines().length;
40
+
41
+ expect(clickControl("Add column")).toBe(true);
42
+ await nextFrame();
43
+ expect(headerPipes()).toBe(beforeCols + 1);
44
+
45
+ expect(clickControl("Add row")).toBe(true);
46
+ await nextFrame();
47
+ expect(tableLines().length).toBe(beforeRows + 1);
48
+
49
+ expect(clickControl("Remove row")).toBe(true);
50
+ await nextFrame();
51
+ expect(tableLines().length).toBe(beforeRows);
52
+
53
+ expect(clickControl("Remove column")).toBe(true);
54
+ await nextFrame();
55
+ expect(headerPipes()).toBe(beforeCols);
56
+ });
56
57
  });
@@ -2,40 +2,41 @@ import { EditorSelection } from "@codemirror/state";
2
2
  import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
3
3
  import { composeStories } from "@storybook/react-vite";
4
4
  import { describe, expect, it } from "vitest";
5
+
5
6
  import {
6
- focusEditor,
7
- getEditorView,
8
- mountStory,
9
- nextFrame,
10
- userEvent,
7
+ focusEditor,
8
+ getEditorView,
9
+ mountStory,
10
+ nextFrame,
11
+ userEvent,
11
12
  } from "../../_setup/e2e-helpers";
12
13
 
13
14
  const Stories = composeStories(MarkdownStories);
14
15
 
15
16
  describe("MarkdownEditor / table formatter", () => {
16
- it("realigns the table columns after typing in a cell", async () => {
17
- mountStory(<Stories.EditorTableFormatter />);
18
- const view = await getEditorView();
19
- await nextFrame();
17
+ it("realigns the table columns after typing in a cell", async () => {
18
+ mountStory(<Stories.EditorTableFormatter />);
19
+ const view = await getEditorView();
20
+ await nextFrame();
20
21
 
21
- const doc = view.state.doc.toString();
22
- const target = doc.indexOf("Ada") + "Ada".length;
23
- expect(target).toBeGreaterThan(0);
22
+ const doc = view.state.doc.toString();
23
+ const target = doc.indexOf("Ada") + "Ada".length;
24
+ expect(target).toBeGreaterThan(0);
24
25
 
25
- view.dispatch({ selection: EditorSelection.cursor(target) });
26
- await focusEditor();
27
- await userEvent.keyboard("lyn"); // "Ada" -> "Adalyn"
28
- await nextFrame();
29
- await nextFrame();
26
+ view.dispatch({ selection: EditorSelection.cursor(target) });
27
+ await focusEditor();
28
+ await userEvent.keyboard("lyn"); // "Ada" -> "Adalyn"
29
+ await nextFrame();
30
+ await nextFrame();
30
31
 
31
- const next = view.state.doc.toString();
32
- // Column 1 grew to fit "Adalyn"; the delimiter row should align to it.
33
- expect(next).toContain("Adalyn");
34
- const lines = next.split("\n");
35
- const widths = lines
36
- .filter((l) => l.includes("|"))
37
- .map((l) => l.indexOf("|", l.indexOf("|") + 1));
38
- // All rows share the same position for the second pipe (aligned column 1).
39
- expect(new Set(widths).size).toBe(1);
40
- });
32
+ const next = view.state.doc.toString();
33
+ // Column 1 grew to fit "Adalyn"; the delimiter row should align to it.
34
+ expect(next).toContain("Adalyn");
35
+ const lines = next.split("\n");
36
+ const widths = lines
37
+ .filter((l) => l.includes("|"))
38
+ .map((l) => l.indexOf("|", l.indexOf("|") + 1));
39
+ // All rows share the same position for the second pipe (aligned column 1).
40
+ expect(new Set(widths).size).toBe(1);
41
+ });
41
42
  });
@@ -3,36 +3,37 @@ import { EditorSelection } from "@codemirror/state";
3
3
  import * as MarkdownStories from "@src/stories/playground/Markdown.stories";
4
4
  import { composeStories } from "@storybook/react-vite";
5
5
  import { describe, expect, it } from "vitest";
6
+
6
7
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
7
8
 
8
9
  const Stories = composeStories(MarkdownStories);
9
10
 
10
11
  describe("MarkdownEditor / undo and redo", () => {
11
- it("restores undone document edits via redo", async () => {
12
- mountStory(<Stories.Editor />);
13
- const view = await getEditorView();
12
+ it("restores undone document edits via redo", async () => {
13
+ mountStory(<Stories.Editor />);
14
+ const view = await getEditorView();
14
15
 
15
- const start = Date.now();
16
- while (view.state.doc.length === 0 && Date.now() - start < 5000) {
17
- await nextFrame();
18
- }
19
- expect(view.state.doc.length).toBeGreaterThan(0);
16
+ const start = Date.now();
17
+ while (view.state.doc.length === 0 && Date.now() - start < 5000) {
18
+ await nextFrame();
19
+ }
20
+ expect(view.state.doc.length).toBeGreaterThan(0);
20
21
 
21
- const before = view.state.doc.toString();
22
+ const before = view.state.doc.toString();
22
23
 
23
- view.dispatch({
24
- changes: { from: view.state.doc.length, insert: "\nredo-marker" },
25
- selection: EditorSelection.cursor(view.state.doc.length),
26
- });
27
- await nextFrame();
24
+ view.dispatch({
25
+ changes: { from: view.state.doc.length, insert: "\nredo-marker" },
26
+ selection: EditorSelection.cursor(view.state.doc.length),
27
+ });
28
+ await nextFrame();
28
29
 
29
- const afterEdit = view.state.doc.toString();
30
- expect(afterEdit).toContain("redo-marker");
30
+ const afterEdit = view.state.doc.toString();
31
+ expect(afterEdit).toContain("redo-marker");
31
32
 
32
- expect(undo(view)).toBe(true);
33
- expect(view.state.doc.toString()).toBe(before);
33
+ expect(undo(view)).toBe(true);
34
+ expect(view.state.doc.toString()).toBe(before);
34
35
 
35
- expect(redo(view)).toBe(true);
36
- expect(view.state.doc.toString()).toBe(afterEdit);
37
- });
36
+ expect(redo(view)).toBe(true);
37
+ expect(view.state.doc.toString()).toBe(afterEdit);
38
+ });
38
39
  });
@@ -4,6 +4,7 @@ import { MarkdownViewer } from "@src/primitives/MarkdownViewer";
4
4
  import { MANTINE_THEME } from "@src/theme/mantine";
5
5
  import { type FC, useState } from "react";
6
6
  import { describe, expect, it } from "vitest";
7
+
7
8
  import { getEditorView, mountStory, nextFrame } from "../../_setup/e2e-helpers";
8
9
  import { md, mdViewer } from "../../_setup/markdown-classes";
9
10
 
@@ -34,157 +35,157 @@ Inside details.
34
35
  `;
35
36
 
36
37
  const ParityLayout: FC = () => {
37
- const [doc] = useState(PARITY_FIXTURE);
38
- return (
39
- <MantineProvider
40
- theme={MANTINE_THEME}
41
- cssVariablesResolver={v8CssVariablesResolver}
42
- forceColorScheme="dark"
43
- >
44
- <Grid gap="md">
45
- <Grid.Col span={{ base: 12, md: 6 }}>
46
- <Paper
47
- p="md"
48
- radius="md"
49
- withBorder
50
- >
51
- <MarkdownEditor
52
- mode="preview"
53
- document={doc}
54
- autoFocus={false}
55
- mih={200}
56
- />
57
- </Paper>
58
- </Grid.Col>
59
- <Grid.Col span={{ base: 12, md: 6 }}>
60
- <Paper
61
- p="md"
62
- radius="md"
63
- withBorder
64
- >
65
- <MarkdownViewer content={doc} />
66
- </Paper>
67
- </Grid.Col>
68
- </Grid>
69
- </MantineProvider>
70
- );
38
+ const [doc] = useState(PARITY_FIXTURE);
39
+ return (
40
+ <MantineProvider
41
+ theme={MANTINE_THEME}
42
+ cssVariablesResolver={v8CssVariablesResolver}
43
+ forceColorScheme="dark"
44
+ >
45
+ <Grid gap="md">
46
+ <Grid.Col span={{ base: 12, md: 6 }}>
47
+ <Paper
48
+ p="md"
49
+ radius="md"
50
+ withBorder
51
+ >
52
+ <MarkdownEditor
53
+ mode="preview"
54
+ document={doc}
55
+ autoFocus={false}
56
+ mih={200}
57
+ />
58
+ </Paper>
59
+ </Grid.Col>
60
+ <Grid.Col span={{ base: 12, md: 6 }}>
61
+ <Paper
62
+ p="md"
63
+ radius="md"
64
+ withBorder
65
+ >
66
+ <MarkdownViewer content={doc} />
67
+ </Paper>
68
+ </Grid.Col>
69
+ </Grid>
70
+ </MantineProvider>
71
+ );
71
72
  };
72
73
 
73
74
  function visibleText(el: Element): string {
74
- const clone = el.cloneNode(true) as Element;
75
+ const clone = el.cloneNode(true) as Element;
75
76
 
76
- for (const n of clone.querySelectorAll(`.${md.markHidden}, .cm-line-folded`)) {
77
- n.remove();
78
- }
77
+ for (const n of clone.querySelectorAll(`.${md.markHidden}, .cm-line-folded`)) {
78
+ n.remove();
79
+ }
79
80
 
80
- return clone.textContent?.replace(/\s+/g, "") ?? "";
81
+ return clone.textContent?.replace(/\s+/g, "") ?? "";
81
82
  }
82
83
 
83
84
  const PARITY_CLASSES = [md.h1, md.h2, md.strong, md.emphasis, md.codeInline, md.link] as const;
84
85
 
85
86
  describe("MarkdownViewer / editor parity", () => {
86
- it("renders checklist text alongside checkboxes in the viewer", async () => {
87
- mountStory(<ParityLayout />);
88
- await getEditorView();
89
- await nextFrame();
90
-
91
- const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
92
- expect(vrEl).not.toBeNull();
93
- if (vrEl === null) return;
94
-
95
- const taskItems = vrEl.querySelectorAll(`li.${md.taskItem}`);
96
- expect(taskItems.length).toBe(2);
97
-
98
- const text = (i: number) =>
99
- (taskItems[i]?.querySelector(`.${md.taskText}`)?.textContent ?? "").trim();
100
- expect(text(0)).toContain("todo one");
101
- expect(text(1)).toContain("todo two");
102
- expect(taskItems[1]?.querySelector(`.${md.strong}`)?.textContent).toBe("with bold");
103
- });
104
-
105
- it("uses the same link colour as the editor", async () => {
106
- mountStory(<ParityLayout />);
107
- const view = await getEditorView();
108
- view.contentDOM.blur();
109
- await nextFrame();
110
-
111
- const cmLink = document.querySelector(`.cm-content .${md.link}`);
112
- const vwLink = document.querySelector(`.${mdViewer.viewerRoot} .${md.link}`);
113
- expect(cmLink).not.toBeNull();
114
- expect(vwLink).not.toBeNull();
115
- if (cmLink === null || vwLink === null) return;
116
-
117
- expect(getComputedStyle(vwLink).color).toBe(getComputedStyle(cmLink).color);
118
- });
119
-
120
- it("matches visible text between preview editor (blurred) and MarkdownViewer", async () => {
121
- mountStory(<ParityLayout />);
122
- const view = await getEditorView();
123
- view.contentDOM.blur();
124
- await nextFrame();
125
- await nextFrame();
126
-
127
- const cmEl = document.querySelector(".cm-content");
128
- const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
129
- expect(cmEl).not.toBeNull();
130
- expect(vrEl).not.toBeNull();
131
- if (cmEl === null || vrEl === null) {
132
- return;
133
- }
134
-
135
- expect(visibleText(vrEl)).toBe(visibleText(cmEl));
136
- });
137
-
138
- it("matches counts for shared structural classes", async () => {
139
- mountStory(<ParityLayout />);
140
- const view = await getEditorView();
141
- view.contentDOM.blur();
142
- await nextFrame();
143
-
144
- const cmEl = document.querySelector(".cm-content");
145
- const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
146
- expect(cmEl).not.toBeNull();
147
- expect(vrEl).not.toBeNull();
148
- if (cmEl === null || vrEl === null) {
149
- return;
150
- }
151
-
152
- for (const cls of PARITY_CLASSES) {
153
- expect(vrEl.querySelectorAll(`.${cls}`).length).toBe(
154
- cmEl.querySelectorAll(`.${cls}`).length,
155
- );
156
- }
157
- });
158
-
159
- it("matches computed font size on first heading", async () => {
160
- mountStory(<ParityLayout />);
161
- const view = await getEditorView();
162
- view.contentDOM.blur();
163
- await nextFrame();
164
-
165
- const cmH1 = document.querySelector(`.cm-content .${md.h1}`);
166
- const vwH1 = document.querySelector(`.${mdViewer.viewerRoot} .${md.h1}`);
167
- expect(cmH1).not.toBeNull();
168
- expect(vwH1).not.toBeNull();
169
- if (cmH1 === null || vwH1 === null) {
170
- return;
171
- }
172
-
173
- const a = getComputedStyle(cmH1);
174
- const b = getComputedStyle(vwH1);
175
- expect(a.fontSize).toBe(b.fontSize);
176
- expect(a.fontWeight).toBe(b.fontWeight);
177
- });
178
-
179
- it("renders HTML details in both editor preview and viewer", async () => {
180
- mountStory(<ParityLayout />);
181
- const view = await getEditorView();
182
- view.contentDOM.blur();
183
- await nextFrame();
184
-
185
- const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
186
- expect(vrEl?.textContent).toContain("Expand");
187
- expect(vrEl?.textContent).toContain("Inside details");
188
- expect(document.querySelector(".cm-content")?.textContent).toContain("Inside details");
189
- });
87
+ it("renders checklist text alongside checkboxes in the viewer", async () => {
88
+ mountStory(<ParityLayout />);
89
+ await getEditorView();
90
+ await nextFrame();
91
+
92
+ const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
93
+ expect(vrEl).not.toBeNull();
94
+ if (vrEl === null) return;
95
+
96
+ const taskItems = vrEl.querySelectorAll(`li.${md.taskItem}`);
97
+ expect(taskItems.length).toBe(2);
98
+
99
+ const text = (i: number) =>
100
+ (taskItems[i]?.querySelector(`.${md.taskText}`)?.textContent ?? "").trim();
101
+ expect(text(0)).toContain("todo one");
102
+ expect(text(1)).toContain("todo two");
103
+ expect(taskItems[1]?.querySelector(`.${md.strong}`)?.textContent).toBe("with bold");
104
+ });
105
+
106
+ it("uses the same link colour as the editor", async () => {
107
+ mountStory(<ParityLayout />);
108
+ const view = await getEditorView();
109
+ view.contentDOM.blur();
110
+ await nextFrame();
111
+
112
+ const cmLink = document.querySelector(`.cm-content .${md.link}`);
113
+ const vwLink = document.querySelector(`.${mdViewer.viewerRoot} .${md.link}`);
114
+ expect(cmLink).not.toBeNull();
115
+ expect(vwLink).not.toBeNull();
116
+ if (cmLink === null || vwLink === null) return;
117
+
118
+ expect(getComputedStyle(vwLink).color).toBe(getComputedStyle(cmLink).color);
119
+ });
120
+
121
+ it("matches visible text between preview editor (blurred) and MarkdownViewer", async () => {
122
+ mountStory(<ParityLayout />);
123
+ const view = await getEditorView();
124
+ view.contentDOM.blur();
125
+ await nextFrame();
126
+ await nextFrame();
127
+
128
+ const cmEl = document.querySelector(".cm-content");
129
+ const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
130
+ expect(cmEl).not.toBeNull();
131
+ expect(vrEl).not.toBeNull();
132
+ if (cmEl === null || vrEl === null) {
133
+ return;
134
+ }
135
+
136
+ expect(visibleText(vrEl)).toBe(visibleText(cmEl));
137
+ });
138
+
139
+ it("matches counts for shared structural classes", async () => {
140
+ mountStory(<ParityLayout />);
141
+ const view = await getEditorView();
142
+ view.contentDOM.blur();
143
+ await nextFrame();
144
+
145
+ const cmEl = document.querySelector(".cm-content");
146
+ const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
147
+ expect(cmEl).not.toBeNull();
148
+ expect(vrEl).not.toBeNull();
149
+ if (cmEl === null || vrEl === null) {
150
+ return;
151
+ }
152
+
153
+ for (const cls of PARITY_CLASSES) {
154
+ expect(vrEl.querySelectorAll(`.${cls}`).length).toBe(
155
+ cmEl.querySelectorAll(`.${cls}`).length,
156
+ );
157
+ }
158
+ });
159
+
160
+ it("matches computed font size on first heading", async () => {
161
+ mountStory(<ParityLayout />);
162
+ const view = await getEditorView();
163
+ view.contentDOM.blur();
164
+ await nextFrame();
165
+
166
+ const cmH1 = document.querySelector(`.cm-content .${md.h1}`);
167
+ const vwH1 = document.querySelector(`.${mdViewer.viewerRoot} .${md.h1}`);
168
+ expect(cmH1).not.toBeNull();
169
+ expect(vwH1).not.toBeNull();
170
+ if (cmH1 === null || vwH1 === null) {
171
+ return;
172
+ }
173
+
174
+ const a = getComputedStyle(cmH1);
175
+ const b = getComputedStyle(vwH1);
176
+ expect(a.fontSize).toBe(b.fontSize);
177
+ expect(a.fontWeight).toBe(b.fontWeight);
178
+ });
179
+
180
+ it("renders HTML details in both editor preview and viewer", async () => {
181
+ mountStory(<ParityLayout />);
182
+ const view = await getEditorView();
183
+ view.contentDOM.blur();
184
+ await nextFrame();
185
+
186
+ const vrEl = document.querySelector(`.${mdViewer.viewerRoot}`);
187
+ expect(vrEl?.textContent).toContain("Expand");
188
+ expect(vrEl?.textContent).toContain("Inside details");
189
+ expect(document.querySelector(".cm-content")?.textContent).toContain("Inside details");
190
+ });
190
191
  });