@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
@@ -12,44 +12,44 @@ import { afterEach, describe, expect, it } from "vitest";
12
12
  const views: EditorView[] = [];
13
13
 
14
14
  afterEach(() => {
15
- for (const v of views.splice(0)) v.destroy();
15
+ for (const v of views.splice(0)) v.destroy();
16
16
  });
17
17
 
18
18
  /** Build a focused, preview-mode editor with the caret at `cursor`. */
19
19
  function preview(doc: string, cursor: number): EditorView {
20
- const state = EditorState.create({
21
- doc,
22
- extensions: [
23
- createMarkdownLanguage({ extensions: [Callout] }),
24
- markdownModeField,
25
- markdownDecorations({ widgetStore: createWidgetStore() }),
26
- ],
27
- });
28
- const parent = document.createElement("div");
29
- document.body.appendChild(parent);
30
- const view = new EditorView({ state, parent });
31
- views.push(view);
32
- view.dispatch({ effects: setMarkdownMode.of("preview") });
33
- view.focus();
34
- view.dispatch({ effects: setMarkdownEditorFocused.of(true) });
35
- view.dispatch({ selection: EditorSelection.cursor(cursor) });
36
- return view;
20
+ const state = EditorState.create({
21
+ doc,
22
+ extensions: [
23
+ createMarkdownLanguage({ extensions: [Callout] }),
24
+ markdownModeField,
25
+ markdownDecorations({ widgetStore: createWidgetStore() }),
26
+ ],
27
+ });
28
+ const parent = document.createElement("div");
29
+ document.body.appendChild(parent);
30
+ const view = new EditorView({ state, parent });
31
+ views.push(view);
32
+ view.dispatch({ effects: setMarkdownMode.of("preview") });
33
+ view.focus();
34
+ view.dispatch({ effects: setMarkdownEditorFocused.of(true) });
35
+ view.dispatch({ selection: EditorSelection.cursor(cursor) });
36
+ return view;
37
37
  }
38
38
 
39
39
  function jsxBlockRanges(view: EditorView): Array<{ from: number; to: number }> {
40
- const out: Array<{ from: number; to: number }> = [];
41
- for (const set of (view as unknown as { docView: { decorations: DecorationSet[] } }).docView
42
- .decorations) {
43
- const cursor = set.iter();
44
- while (cursor.value) {
45
- const widget = (cursor.value.spec as { widget?: Decoration }).widget;
46
- if (widget instanceof JsxBlockWidget) {
47
- out.push({ from: cursor.from, to: cursor.to });
48
- }
49
- cursor.next();
50
- }
51
- }
52
- return out;
40
+ const out: Array<{ from: number; to: number }> = [];
41
+ for (const set of (view as unknown as { docView: { decorations: DecorationSet[] } }).docView
42
+ .decorations) {
43
+ const cursor = set.iter();
44
+ while (cursor.value) {
45
+ const widget = (cursor.value.spec as { widget?: Decoration }).widget;
46
+ if (widget instanceof JsxBlockWidget) {
47
+ out.push({ from: cursor.from, to: cursor.to });
48
+ }
49
+ cursor.next();
50
+ }
51
+ }
52
+ return out;
53
53
  }
54
54
 
55
55
  const TABS = `<Tabs syncKey="demo">
@@ -67,26 +67,26 @@ Beta para one.
67
67
  const TABS_WITH_PROSE = `Intro line.\n\n${TABS}`;
68
68
 
69
69
  describe("MarkdownEditor / JSX block widget", () => {
70
- it("renders a multi-paragraph Tabs block as a single widget when the caret is outside", () => {
71
- const view = preview(TABS_WITH_PROSE, 0); // caret in the intro line
72
- const ranges = jsxBlockRanges(view);
73
- expect(ranges).toHaveLength(1);
74
- // Covers the whole <Tabs>…</Tabs> span, not a fragment of it.
75
- const tabsStart = TABS_WITH_PROSE.indexOf("<Tabs");
76
- expect(ranges[0]?.from).toBe(tabsStart);
77
- expect(ranges[0]?.to).toBe(TABS_WITH_PROSE.length);
78
- });
70
+ it("renders a multi-paragraph Tabs block as a single widget when the caret is outside", () => {
71
+ const view = preview(TABS_WITH_PROSE, 0); // caret in the intro line
72
+ const ranges = jsxBlockRanges(view);
73
+ expect(ranges).toHaveLength(1);
74
+ // Covers the whole <Tabs>…</Tabs> span, not a fragment of it.
75
+ const tabsStart = TABS_WITH_PROSE.indexOf("<Tabs");
76
+ expect(ranges[0]?.from).toBe(tabsStart);
77
+ expect(ranges[0]?.to).toBe(TABS_WITH_PROSE.length);
78
+ });
79
79
 
80
- it("reveals the entire block as source (no partial widgets) when the caret is inside", () => {
81
- // Caret inside the first tab's content.
82
- const inside = TABS_WITH_PROSE.indexOf("Alpha para one.") + 3;
83
- const view = preview(TABS_WITH_PROSE, inside);
84
- expect(jsxBlockRanges(view)).toHaveLength(0);
85
- });
80
+ it("reveals the entire block as source (no partial widgets) when the caret is inside", () => {
81
+ // Caret inside the first tab's content.
82
+ const inside = TABS_WITH_PROSE.indexOf("Alpha para one.") + 3;
83
+ const view = preview(TABS_WITH_PROSE, inside);
84
+ expect(jsxBlockRanges(view)).toHaveLength(0);
85
+ });
86
86
 
87
- it("keeps nested TabItem tags raw when the caret is on the closing tag line", () => {
88
- const onClose = TABS_WITH_PROSE.indexOf("</Tabs>") + 1;
89
- const view = preview(TABS_WITH_PROSE, onClose);
90
- expect(jsxBlockRanges(view)).toHaveLength(0);
91
- });
87
+ it("keeps nested TabItem tags raw when the caret is on the closing tag line", () => {
88
+ const onClose = TABS_WITH_PROSE.indexOf("</Tabs>") + 1;
89
+ const view = preview(TABS_WITH_PROSE, onClose);
90
+ expect(jsxBlockRanges(view)).toHaveLength(0);
91
+ });
92
92
  });
@@ -1,88 +1,88 @@
1
1
  import {
2
- JSX_ATTR_NAME_NODE,
3
- JSX_ATTR_STRING_NODE,
4
- JSX_EXPRESSION_CONTENT_NODE,
5
- JSX_EXPRESSION_MARK_NODE,
2
+ JSX_ATTR_NAME_NODE,
3
+ JSX_ATTR_STRING_NODE,
4
+ JSX_EXPRESSION_CONTENT_NODE,
5
+ JSX_EXPRESSION_MARK_NODE,
6
6
  } from "@src/lib/markdown/grammar/jsx-tag";
7
7
  import { createMarkdownLanguage } from "@src/lib/markdown/grammar/language";
8
8
  import { describe, expect, it } from "vitest";
9
9
 
10
10
  function parse(text: string) {
11
- const support = createMarkdownLanguage({ addKeymap: false, pasteURLAsLink: false });
12
- return support.language.parser.parse(text);
11
+ const support = createMarkdownLanguage({ addKeymap: false, pasteURLAsLink: false });
12
+ return support.language.parser.parse(text);
13
13
  }
14
14
 
15
15
  function nodeNames(text: string): string[] {
16
- const tree = parse(text);
17
- const names: string[] = [];
18
- tree.iterate({
19
- enter: (n) => {
20
- names.push(n.name);
21
- },
22
- });
23
- return names;
16
+ const tree = parse(text);
17
+ const names: string[] = [];
18
+ tree.iterate({
19
+ enter: (n) => {
20
+ names.push(n.name);
21
+ },
22
+ });
23
+ return names;
24
24
  }
25
25
 
26
26
  function htmlTagSpan(text: string): { from: number; to: number } | null {
27
- const tree = parse(text);
28
- let span: { from: number; to: number } | null = null;
29
- tree.iterate({
30
- enter: (n) => {
31
- if (n.name === "HTMLTag") span = { from: n.from, to: n.to };
32
- },
33
- });
34
- return span;
27
+ const tree = parse(text);
28
+ let span: { from: number; to: number } | null = null;
29
+ tree.iterate({
30
+ enter: (n) => {
31
+ if (n.name === "HTMLTag") span = { from: n.from, to: n.to };
32
+ },
33
+ });
34
+ return span;
35
35
  }
36
36
 
37
37
  describe("JsxTag grammar extension", () => {
38
- it("keeps plain quoted attributes on the built-in HTMLTag parser", () => {
39
- expect(nodeNames('<Since v="1" />')).toEqual(["Document", "Paragraph", "HTMLTag"]);
40
- expect(nodeNames('<Since v="1" />')).not.toContain(JSX_EXPRESSION_CONTENT_NODE);
41
- });
38
+ it("keeps plain quoted attributes on the built-in HTMLTag parser", () => {
39
+ expect(nodeNames('<Since v="1" />')).toEqual(["Document", "Paragraph", "HTMLTag"]);
40
+ expect(nodeNames('<Since v="1" />')).not.toContain(JSX_EXPRESSION_CONTENT_NODE);
41
+ });
42
42
 
43
- it("parses a full HTMLTag when the attribute value is a JSX expression", () => {
44
- const text = '<Component prop={"a"} />';
45
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
46
- expect(nodeNames(text)).toContain(JSX_EXPRESSION_CONTENT_NODE);
47
- expect(nodeNames(text)).toContain(JSX_EXPRESSION_MARK_NODE);
48
- });
43
+ it("parses a full HTMLTag when the attribute value is a JSX expression", () => {
44
+ const text = '<Component prop={"a"} />';
45
+ expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
46
+ expect(nodeNames(text)).toContain(JSX_EXPRESSION_CONTENT_NODE);
47
+ expect(nodeNames(text)).toContain(JSX_EXPRESSION_MARK_NODE);
48
+ });
49
49
 
50
- it("does not truncate at > inside a JSX expression", () => {
51
- const text = "<Component prop={foo > bar} />";
52
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
53
- });
50
+ it("does not truncate at > inside a JSX expression", () => {
51
+ const text = "<Component prop={foo > bar} />";
52
+ expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
53
+ });
54
54
 
55
- it("parses multiple JSX tags on one line", () => {
56
- const text = '<Tabs defaultValue={"a"}><TabItem label={x} /></Tabs>';
57
- const tree = parse(text);
58
- const tags: string[] = [];
59
- tree.iterate({
60
- enter: (n) => {
61
- if (n.name === "HTMLTag") tags.push(text.slice(n.from, n.to));
62
- },
63
- });
64
- expect(tags).toEqual(['<Tabs defaultValue={"a"}>', "<TabItem label={x} />", "</Tabs>"]);
65
- });
55
+ it("parses multiple JSX tags on one line", () => {
56
+ const text = '<Tabs defaultValue={"a"}><TabItem label={x} /></Tabs>';
57
+ const tree = parse(text);
58
+ const tags: string[] = [];
59
+ tree.iterate({
60
+ enter: (n) => {
61
+ if (n.name === "HTMLTag") tags.push(text.slice(n.from, n.to));
62
+ },
63
+ });
64
+ expect(tags).toEqual(['<Tabs defaultValue={"a"}>', "<TabItem label={x} />", "</Tabs>"]);
65
+ });
66
66
 
67
- it("highlights quoted attributes that follow a JSX expression", () => {
68
- const text = '<Since v={"2.0.0"} t="true" b="false" />';
69
- expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
67
+ it("highlights quoted attributes that follow a JSX expression", () => {
68
+ const text = '<Since v={"2.0.0"} t="true" b="false" />';
69
+ expect(htmlTagSpan(text)).toEqual({ from: 0, to: text.length });
70
70
 
71
- const tree = parse(text);
72
- const names = collectNodes(tree, text, JSX_ATTR_NAME_NODE);
73
- const strings = collectNodes(tree, text, JSX_ATTR_STRING_NODE);
71
+ const tree = parse(text);
72
+ const names = collectNodes(tree, text, JSX_ATTR_NAME_NODE);
73
+ const strings = collectNodes(tree, text, JSX_ATTR_STRING_NODE);
74
74
 
75
- expect(names).toEqual(["v", "t", "b"]);
76
- expect(strings).toEqual(['"true"', '"false"']);
77
- });
75
+ expect(names).toEqual(["v", "t", "b"]);
76
+ expect(strings).toEqual(['"true"', '"false"']);
77
+ });
78
78
  });
79
79
 
80
80
  function collectNodes(tree: ReturnType<typeof parse>, text: string, name: string): string[] {
81
- const out: string[] = [];
82
- tree.iterate({
83
- enter: (n) => {
84
- if (n.name === name) out.push(text.slice(n.from, n.to));
85
- },
86
- });
87
- return out;
81
+ const out: string[] = [];
82
+ tree.iterate({
83
+ enter: (n) => {
84
+ if (n.name === name) out.push(text.slice(n.from, n.to));
85
+ },
86
+ });
87
+ return out;
88
88
  }
@@ -8,88 +8,88 @@ import { afterEach, describe, expect, it } from "vitest";
8
8
  let view: EditorView;
9
9
 
10
10
  afterEach(() => {
11
- view?.destroy();
11
+ view?.destroy();
12
12
  });
13
13
 
14
14
  function createView(doc: string, cursor: number): EditorView {
15
- const state = EditorState.create({
16
- doc,
17
- selection: EditorSelection.cursor(cursor),
18
- extensions: [createMarkdownLanguage({ extensions: [Callout] })],
19
- });
20
- const parent = document.createElement("div");
21
- document.body.appendChild(parent);
22
- view = new EditorView({ state, parent });
23
- return view;
15
+ const state = EditorState.create({
16
+ doc,
17
+ selection: EditorSelection.cursor(cursor),
18
+ extensions: [createMarkdownLanguage({ extensions: [Callout] })],
19
+ });
20
+ const parent = document.createElement("div");
21
+ document.body.appendChild(parent);
22
+ view = new EditorView({ state, parent });
23
+ return view;
24
24
  }
25
25
 
26
26
  /** Index just after the marker + space of the line containing `needle`. */
27
27
  function cursorInItem(doc: string, needle: string): number {
28
- return doc.indexOf(needle) + 1;
28
+ return doc.indexOf(needle) + 1;
29
29
  }
30
30
 
31
31
  describe("indentMarkdownList", () => {
32
- it("indents an item under its preceding sibling", () => {
33
- const doc = "- one\n- two";
34
- const v = createView(doc, cursorInItem(doc, "two"));
35
- const handled = indentMarkdownList(v, false);
36
- expect(handled).toBe(true);
37
- expect(v.state.doc.toString()).toBe("- one\n - two");
38
- });
32
+ it("indents an item under its preceding sibling", () => {
33
+ const doc = "- one\n- two";
34
+ const v = createView(doc, cursorInItem(doc, "two"));
35
+ const handled = indentMarkdownList(v, false);
36
+ expect(handled).toBe(true);
37
+ expect(v.state.doc.toString()).toBe("- one\n - two");
38
+ });
39
39
 
40
- it("consumes Tab but does not change the first item (no preceding sibling)", () => {
41
- const doc = "- one\n- two";
42
- const v = createView(doc, cursorInItem(doc, "one"));
43
- const handled = indentMarkdownList(v, false);
44
- expect(handled).toBe(true);
45
- expect(v.state.doc.toString()).toBe(doc);
46
- });
40
+ it("consumes Tab but does not change the first item (no preceding sibling)", () => {
41
+ const doc = "- one\n- two";
42
+ const v = createView(doc, cursorInItem(doc, "one"));
43
+ const handled = indentMarkdownList(v, false);
44
+ expect(handled).toBe(true);
45
+ expect(v.state.doc.toString()).toBe(doc);
46
+ });
47
47
 
48
- it("outdents a nested item to its parent level", () => {
49
- const doc = "- one\n - two";
50
- const v = createView(doc, cursorInItem(doc, "two"));
51
- const handled = indentMarkdownList(v, true);
52
- expect(handled).toBe(true);
53
- expect(v.state.doc.toString()).toBe("- one\n- two");
54
- });
48
+ it("outdents a nested item to its parent level", () => {
49
+ const doc = "- one\n - two";
50
+ const v = createView(doc, cursorInItem(doc, "two"));
51
+ const handled = indentMarkdownList(v, true);
52
+ expect(handled).toBe(true);
53
+ expect(v.state.doc.toString()).toBe("- one\n- two");
54
+ });
55
55
 
56
- it("consumes Shift-Tab but does not outdent a top-level item", () => {
57
- const doc = "- one\n- two";
58
- const v = createView(doc, cursorInItem(doc, "two"));
59
- const handled = indentMarkdownList(v, true);
60
- expect(handled).toBe(true);
61
- expect(v.state.doc.toString()).toBe(doc);
62
- });
56
+ it("consumes Shift-Tab but does not outdent a top-level item", () => {
57
+ const doc = "- one\n- two";
58
+ const v = createView(doc, cursorInItem(doc, "two"));
59
+ const handled = indentMarkdownList(v, true);
60
+ expect(handled).toBe(true);
61
+ expect(v.state.doc.toString()).toBe(doc);
62
+ });
63
63
 
64
- it("returns false outside a list", () => {
65
- const doc = "just a paragraph";
66
- const v = createView(doc, 4);
67
- expect(indentMarkdownList(v, false)).toBe(false);
68
- expect(indentMarkdownList(v, true)).toBe(false);
69
- });
64
+ it("returns false outside a list", () => {
65
+ const doc = "just a paragraph";
66
+ const v = createView(doc, 4);
67
+ expect(indentMarkdownList(v, false)).toBe(false);
68
+ expect(indentMarkdownList(v, true)).toBe(false);
69
+ });
70
70
 
71
- it("indents ordered list items by the marker width", () => {
72
- const doc = "1. one\n2. two";
73
- const v = createView(doc, cursorInItem(doc, "two"));
74
- const handled = indentMarkdownList(v, false);
75
- expect(handled).toBe(true);
76
- expect(v.state.doc.toString()).toBe("1. one\n 2. two");
77
- });
71
+ it("indents ordered list items by the marker width", () => {
72
+ const doc = "1. one\n2. two";
73
+ const v = createView(doc, cursorInItem(doc, "two"));
74
+ const handled = indentMarkdownList(v, false);
75
+ expect(handled).toBe(true);
76
+ expect(v.state.doc.toString()).toBe("1. one\n 2. two");
77
+ });
78
78
 
79
- it("indents an item together with its nested children", () => {
80
- const doc = "- one\n- two\n - child";
81
- const v = createView(doc, cursorInItem(doc, "two"));
82
- const handled = indentMarkdownList(v, false);
83
- expect(handled).toBe(true);
84
- expect(v.state.doc.toString()).toBe("- one\n - two\n - child");
85
- });
79
+ it("indents an item together with its nested children", () => {
80
+ const doc = "- one\n- two\n - child";
81
+ const v = createView(doc, cursorInItem(doc, "two"));
82
+ const handled = indentMarkdownList(v, false);
83
+ expect(handled).toBe(true);
84
+ expect(v.state.doc.toString()).toBe("- one\n - two\n - child");
85
+ });
86
86
 
87
- it("keeps the cursor with the indented text", () => {
88
- const doc = "- one\n- two";
89
- const start = doc.indexOf("two");
90
- const v = createView(doc, start);
91
- indentMarkdownList(v, false);
92
- // Two spaces inserted before the line shift the cursor right by two.
93
- expect(v.state.selection.main.head).toBe(start + 2);
94
- });
87
+ it("keeps the cursor with the indented text", () => {
88
+ const doc = "- one\n- two";
89
+ const start = doc.indexOf("two");
90
+ const v = createView(doc, start);
91
+ indentMarkdownList(v, false);
92
+ // Two spaces inserted before the line shift the cursor right by two.
93
+ expect(v.state.selection.main.head).toBe(start + 2);
94
+ });
95
95
  });