ngwr 14.9.1 → 14.10.0

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.
@@ -163,7 +163,22 @@
163
163
  display: none;
164
164
  }
165
165
 
166
- &--disabled &__surface {
166
+ // The document a read-only editor draws instead of mounting. It is the control
167
+ // itself there, and the only one of the two that needs a focus ring: a mounted
168
+ // surface says where focus is with a caret, and this has none. Inset, because
169
+ // the box runs flush to the editor's own border and an outset ring would paint
170
+ // over it — the override `list` and `window` make for the same reason.
171
+ &__preview--static {
172
+ cursor: default;
173
+
174
+ &:focus-visible {
175
+ @include theme.focus-ring;
176
+ outline-offset: -2px;
177
+ }
178
+ }
179
+
180
+ &--disabled &__surface,
181
+ &--disabled &__preview--static {
167
182
  cursor: not-allowed;
168
183
  }
169
184
 
@@ -9,8 +9,17 @@ import { WrPopoverHarness } from 'ngwr/popover/testing';
9
9
  * Use of this source code is governed by an MIT-style license that can be
10
10
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
11
11
  */
12
- /** The text surface ProseMirror mounts on. */
12
+ /** The text surface ProseMirror mounts on. Not rendered at all while the editor is read-only. */
13
13
  const SURFACE = '.wr-editor__surface';
14
+ /**
15
+ * The document a read-only editor draws instead of mounting. It exists in that
16
+ * one state and in no other, so finding it IS the answer to "is this editor
17
+ * read-only" — the state cannot carry `aria-readonly`, since `role="group"`
18
+ * does not allow one.
19
+ */
20
+ const STATIC = '.wr-editor__preview--static';
21
+ /** Whichever of the two is on screen — the element carrying the editor's id, name and ARIA. */
22
+ const CONTROL = `:is(${SURFACE}, ${STATIC})`;
14
23
  /** One toolbar button. The separators are `<span>`s and never match. */
15
24
  const TOOL = '.wr-editor__tool';
16
25
  /**
@@ -24,7 +33,7 @@ const LINK_TOOL = `${TOOL}[aria-haspopup="dialog"]`;
24
33
  * nothing else: a task item's box and its screen-reader words sit OUTSIDE the
25
34
  * paragraph, beside it.
26
35
  */
27
- const TEXTBLOCKS = `${SURFACE} :is(p, h1, h2, h3, pre)`;
36
+ const TEXTBLOCKS = `${CONTROL} :is(p, h1, h2, h3, pre)`;
28
37
  /**
29
38
  * The modifier ProseMirror's keymap reads `Mod` as. It decides from
30
39
  * `navigator.platform` itself, once, when `prosemirror-keymap` loads — so this asks
@@ -75,10 +84,24 @@ function primaryModifier() {
75
84
  * {@link getTool} hands back a `WrButtonHarness`; what that harness does not read
76
85
  * (`aria-pressed`, `aria-keyshortcuts`, the roving tab stop) is answered here.
77
86
  *
78
- * Every method reads the MOUNTED surface. ProseMirror takes it over in
87
+ * **A read-only editor has no surface at all**, and that is the state to know
88
+ * about before reading anything here. `readonly` set before the editor mounts
89
+ * means ProseMirror is never created: the document is drawn as ordinary markup
90
+ * in a focusable `role="group"`, which takes over the id, the name and the tab
91
+ * stop. So the reads answer from whichever element is on screen —
92
+ * {@link getText}, {@link getLabel}, {@link isDisabled}, {@link isInvalid},
93
+ * {@link focus} — while everything that needs a live view ({@link setText},
94
+ * {@link paste}, {@link pressKey}, {@link selectAll}) throws a sentence saying
95
+ * there is nothing to type into rather than passing on a surface that is not
96
+ * there. {@link isMounted} is the question itself, and {@link isReadonly} keeps
97
+ * answering: the static document exists in that one state, which is how it is
98
+ * read, since ARIA allows no `aria-readonly` on a group.
99
+ *
100
+ * Otherwise every method reads the MOUNTED surface. ProseMirror takes it over in
79
101
  * `afterNextRender`, which the harness's own stabilising lets run; under a server
80
- * `PLATFORM_ID` it never does, and the static preview the server renders is not
81
- * something this harness pretends to be the editor.
102
+ * `PLATFORM_ID` it never does, and the transient preview the server renders — the
103
+ * one a browser is about to replace — is not something this harness pretends to
104
+ * be the editor.
82
105
  *
83
106
  * **A spec under jsdom installs one stub first.** After an edit made while the
84
107
  * surface has focus, ProseMirror scrolls the selection into view, which measures a
@@ -121,21 +144,29 @@ class WrEditorHarness extends ComponentHarness {
121
144
  .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)
122
145
  .addOption('readonly', options.readonly, async (harness, readonly) => (await harness.isReadonly()) === readonly);
123
146
  }
124
- surface = this.locatorFor(SURFACE);
147
+ /** The element carrying the editor's id, name and ARIA — the surface, or the static document. */
148
+ control = this.locatorFor(CONTROL);
149
+ liveSurface = this.locatorForOptional(SURFACE);
150
+ staticDoc = this.locatorForOptional(STATIC);
125
151
  // ---------------------------------------------------------------------------
126
152
  // The surface
127
153
  // ---------------------------------------------------------------------------
128
154
  /**
129
- * Whether ProseMirror owns the surface yet — read from the `contenteditable` it
155
+ * Whether ProseMirror owns a surface yet — read from the `contenteditable` it
130
156
  * writes there, which nothing else does.
131
157
  *
132
- * `true` in any browser-platform spec by the time a harness method runs. `false`
133
- * under a server `PLATFORM_ID`, where the editor is the static preview for good —
134
- * and every other method here throws, rather than answer about a preview as if it
135
- * were the editor.
158
+ * `true` in any browser-platform spec on an editor that is not read-only, by the
159
+ * time a harness method runs. `false` in the two states where no mount is coming:
160
+ * under a server `PLATFORM_ID`, where what shows is the transient preview and
161
+ * every other method here throws rather than answer about it as if it were the
162
+ * editor; and on a READ-ONLY editor, which never mounts by design and whose
163
+ * static document is read here like any other — this is the method that tells
164
+ * those two apart from a mounted one, and {@link isReadonly} tells them apart
165
+ * from each other.
136
166
  */
137
167
  async isMounted() {
138
- return (await (await this.surface()).getAttribute('contenteditable')) !== null;
168
+ const surface = await this.liveSurface();
169
+ return !!surface && (await surface.getAttribute('contenteditable')) !== null;
139
170
  }
140
171
  /**
141
172
  * The text the surface DRAWS: every textblock (paragraph, heading, code block) in
@@ -150,15 +181,20 @@ class WrEditorHarness extends ComponentHarness {
150
181
  *
151
182
  * This is the drawn half of the two questions an editor answers; the class docs say
152
183
  * why the other half, the value, is read from the model instead.
184
+ *
185
+ * A READ-ONLY editor draws its document without ProseMirror, and this reads that
186
+ * document — it is the editor there, not a stand-in for one.
153
187
  */
154
188
  async getText() {
155
- await this.mountedSurface('getText');
189
+ await this.drawnDocument('getText');
156
190
  const blocks = await this.locatorForAll(TEXTBLOCKS)();
157
191
  return (await parallel(() => blocks.map(block => block.text()))).join('\n');
158
192
  }
159
193
  /**
160
- * The surface's accessible name — `ariaLabel` when one is bound, else the
161
- * surrounding `<wr-form-field>`'s label, else the catalog's `editor.label`.
194
+ * The editor's accessible name — `ariaLabel` when one is bound, else the
195
+ * surrounding `<wr-form-field>`'s label, else the catalog's `editor.label`. Read
196
+ * off whichever element carries it: the surface, or a read-only editor's static
197
+ * document, which takes the name over with the tab stop.
162
198
  *
163
199
  * A field's label reaches the surface through `aria-labelledby`, never `<label for>`:
164
200
  * a contenteditable is not labelable. So the reference is resolved the way assistive
@@ -167,8 +203,8 @@ class WrEditorHarness extends ComponentHarness {
167
203
  * catching.
168
204
  */
169
205
  async getLabel() {
170
- const surface = await this.surface();
171
- const labelledBy = await surface.getAttribute('aria-labelledby');
206
+ const control = await this.control();
207
+ const labelledBy = await control.getAttribute('aria-labelledby');
172
208
  if (labelledBy) {
173
209
  const root = this.documentRootLocatorFactory();
174
210
  const labels = await parallel(() => labelledBy
@@ -180,7 +216,7 @@ class WrEditorHarness extends ComponentHarness {
180
216
  }));
181
217
  return labels.filter(Boolean).join(' ') || null;
182
218
  }
183
- return surface.getAttribute('aria-label');
219
+ return control.getAttribute('aria-label');
184
220
  }
185
221
  /**
186
222
  * The placeholder as announced — `aria-placeholder` — or `null` when there is none
@@ -190,36 +226,51 @@ class WrEditorHarness extends ComponentHarness {
190
226
  * `placeholder` is bound: the component publishes it only while the document is
191
227
  * empty, the moment it is also drawn. A document holding an empty heading still
192
228
  * counts as empty.
229
+ *
230
+ * Always `null` on a READ-ONLY editor, however `placeholder` is bound, and that is
231
+ * the truth rather than a hole: there is no input to hint at, and
232
+ * `aria-placeholder` is not among the attributes the static document's
233
+ * `role="group"` allows, so the component draws none there either.
193
234
  */
194
235
  async getPlaceholder() {
195
- return (await this.surface()).getAttribute('aria-placeholder');
236
+ return (await this.control()).getAttribute('aria-placeholder');
196
237
  }
197
238
  /**
198
- * Whether the editor is disabled — the surface's `aria-disabled`.
239
+ * Whether the editor is disabled — `aria-disabled`, on the surface or on the static
240
+ * document, whichever is drawn.
199
241
  *
200
- * Read from the surface rather than the `wr-editor--disabled` host class, because a
242
+ * Read from there rather than from the `wr-editor--disabled` host class, because a
201
243
  * contenteditable has no `disabled` property: the attribute is what a screen reader
202
244
  * is told, and ProseMirror's own `contenteditable="false"` is what turns the
203
245
  * keyboard away. The component sets both.
204
246
  */
205
247
  async isDisabled() {
206
- return (await (await this.surface()).getAttribute('aria-disabled')) === 'true';
248
+ return (await (await this.control()).getAttribute('aria-disabled')) === 'true';
207
249
  }
208
250
  /**
209
- * Whether the editor shows its document and refuses edits — the surface's
210
- * `aria-readonly`, which `role="textbox"` allows. Read-only keeps the tab stop, so
211
- * {@link focus} still lands; disabled does not.
251
+ * Whether the editor shows its document and refuses edits. Read-only keeps the tab
252
+ * stop, so {@link focus} still lands; disabled does not.
253
+ *
254
+ * Two DOM facts answer it, because the two shapes of read-only look nothing alike.
255
+ * An editor that was read-only before it could mount has no surface at all, and the
256
+ * static document it draws instead is drawn in that state and in no other — so its
257
+ * presence is the answer, and it is the only one available: ARIA defines no
258
+ * `aria-readonly` for `role="group"`, and `aria-disabled` would say something else.
259
+ * One that mounted first keeps its surface and reports `aria-readonly` there, which
260
+ * `role="textbox"` does allow.
212
261
  */
213
262
  async isReadonly() {
214
- return (await (await this.surface()).getAttribute('aria-readonly')) === 'true';
263
+ if (await this.staticDoc())
264
+ return true;
265
+ return (await (await this.control()).getAttribute('aria-readonly')) === 'true';
215
266
  }
216
267
  /**
217
- * Whether the surface is announced invalid — the `aria-invalid` the surrounding
268
+ * Whether the editor is announced invalid — the `aria-invalid` the surrounding
218
269
  * `<wr-form-field>` hands it when a bound field fails validation and has been
219
270
  * touched. An editor outside a field is never invalid.
220
271
  */
221
272
  async isInvalid() {
222
- return (await (await this.surface()).getAttribute('aria-invalid')) === 'true';
273
+ return (await (await this.control()).getAttribute('aria-invalid')) === 'true';
223
274
  }
224
275
  /**
225
276
  * Replace the whole document with `text`, as one paragraph of plain text.
@@ -235,8 +286,9 @@ class WrEditorHarness extends ComponentHarness {
235
286
  * An empty `text` empties the document, which writes `''` (`null` in `json` format).
236
287
  *
237
288
  * Throws on a disabled or read-only editor, where no user could change a word — the
238
- * surface is not contenteditable then, and a write the DOM accepted anyway would be
239
- * a green spec for something that cannot happen.
289
+ * surface is not contenteditable then, and is not even rendered when read-only came
290
+ * first, so a write the DOM accepted anyway would be a green spec for something that
291
+ * cannot happen.
240
292
  */
241
293
  async setText(text) {
242
294
  const surface = await this.writableSurface('setText');
@@ -248,10 +300,11 @@ class WrEditorHarness extends ComponentHarness {
248
300
  * Paste `text` as plain text at the selection, replacing whatever is selected — a
249
301
  * `paste` event carrying `text/plain`, handled by ProseMirror's own paste path.
250
302
  *
251
- * On a READ-ONLY editor the event is still delivered, because a browser delivers it:
252
- * the refusal is the editor's to make, and asserting the value did not change is how
253
- * a spec proves it did. Throws on a disabled one, whose surface cannot hold focus for
254
- * a paste to arrive at.
303
+ * On an editor that was MOUNTED before it went read-only the event is still
304
+ * delivered, because a browser delivers it: the refusal is the editor's to make, and
305
+ * asserting the value did not change is how a spec proves it did. One that was
306
+ * read-only from the start has no surface for a paste to arrive at, and throws
307
+ * saying so. Throws on a disabled one too, whose surface cannot hold focus.
255
308
  */
256
309
  async paste(text) {
257
310
  await this.dispatchPaste('paste', { 'text/plain': text });
@@ -276,7 +329,8 @@ class WrEditorHarness extends ComponentHarness {
276
329
  * Throws on a read-only or disabled editor: ProseMirror hands the keyboard back to
277
330
  * the browser once the surface is not editable, and the browser's native select-all
278
331
  * is exactly what jsdom does not have, so the chord would select nothing and say so
279
- * to no one.
332
+ * to no one — and an editor that was read-only before it could mount has no surface
333
+ * to aim the chord at in the first place.
280
334
  */
281
335
  async selectAll() {
282
336
  if (await this.isReadonly()) {
@@ -313,28 +367,28 @@ class WrEditorHarness extends ComponentHarness {
313
367
  await surface.sendKeys(modifiers, key);
314
368
  }
315
369
  /**
316
- * Move focus to the text surface. A read-only editor keeps its tab stop and takes
317
- * it.
370
+ * Move focus to the text — the mounted surface, or a read-only editor's static
371
+ * document, which keeps a tab stop for exactly this.
318
372
  *
319
- * Throws on a disabled one rather than focusing it. In a browser its surface cannot
320
- * take focus — no `tabindex`, and `contenteditable="false"` — but jsdom counts ANY
321
- * `contenteditable` attribute as focusable, `false` included, so the focus would
322
- * land in the spec and never in the app.
373
+ * Throws on a disabled editor rather than focusing it. In a browser its surface
374
+ * cannot take focus — no `tabindex`, and `contenteditable="false"` — but jsdom
375
+ * counts ANY `contenteditable` attribute as focusable, `false` included, so the
376
+ * focus would land in the spec and never in the app.
323
377
  */
324
378
  async focus() {
325
- return (await this.focusableSurface('focus')).focus();
379
+ return (await this.focusableControl('focus')).focus();
326
380
  }
327
381
  /**
328
- * Take focus away from the surface — the whole control losing focus, which is what
382
+ * Take focus away from the text — the whole control losing focus, which is what
329
383
  * emits `touch` and lets a bound field show its error. Focus moving to the editor's
330
384
  * own toolbar or link panel is not a blur of the control, and does not emit it.
331
385
  */
332
386
  async blur() {
333
- return (await this.surface()).blur();
387
+ return (await this.control()).blur();
334
388
  }
335
- /** Whether the text surface holds focus. A toolbar button holding it is not the surface. */
389
+ /** Whether the text holds focus. A toolbar button holding it is not the text. */
336
390
  async isFocused() {
337
- return (await this.surface()).isFocused();
391
+ return (await this.control()).isFocused();
338
392
  }
339
393
  // ---------------------------------------------------------------------------
340
394
  // The toolbar
@@ -507,15 +561,29 @@ class WrEditorHarness extends ComponentHarness {
507
561
  // ---------------------------------------------------------------------------
508
562
  // Internals
509
563
  // ---------------------------------------------------------------------------
510
- /** The surface, or a sentence saying the editor is still the server's static preview. */
564
+ /** The element drawing the editor's own document — the mounted surface, or the static one. */
565
+ async drawnDocument(method) {
566
+ if ((await this.isMounted()) || (await this.staticDoc()))
567
+ return this.control();
568
+ throw new Error(`WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +
569
+ 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the transient ' +
570
+ 'preview, and this harness does not answer for it as if it were the editor. A read-only editor is the ' +
571
+ 'other way round: it never mounts by design, and the document it draws IS the editor, so it reads here ' +
572
+ 'like any other.');
573
+ }
574
+ /** The surface, or a sentence saying what is on screen instead of one. */
511
575
  async mountedSurface(method) {
512
- const surface = await this.surface();
513
- if ((await surface.getAttribute('contenteditable')) === null) {
514
- throw new Error(`WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +
515
- 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the static ' +
516
- 'preview, and this harness does not answer for it as if it were the editor.');
576
+ const surface = await this.liveSurface();
577
+ if (surface && (await surface.getAttribute('contenteditable')) !== null)
578
+ return surface;
579
+ if (await this.staticDoc()) {
580
+ throw new Error(`WrEditorHarness.${method}(): the editor is read-only, so ProseMirror was never mounted and there is no ` +
581
+ 'contenteditable surface for a key, a paste or a write to arrive at. Assert isReadonly(), read the ' +
582
+ 'document with getText(), or lift readonly first — the editor mounts then.');
517
583
  }
518
- return surface;
584
+ throw new Error(`WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +
585
+ 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the transient ' +
586
+ 'preview, and this harness does not answer for it as if it were the editor.');
519
587
  }
520
588
  /** The surface, when a user could still reach it with the keyboard. */
521
589
  async focusableSurface(method) {
@@ -526,6 +594,17 @@ class WrEditorHarness extends ComponentHarness {
526
594
  }
527
595
  return surface;
528
596
  }
597
+ /** Whatever a keyboard user could put focus on: the surface, or the static document. */
598
+ async focusableControl(method) {
599
+ const staticDoc = await this.staticDoc();
600
+ if (!staticDoc)
601
+ return this.focusableSurface(method);
602
+ if (await this.isDisabled()) {
603
+ throw new Error(`WrEditorHarness.${method}(): the editor is disabled — the document it draws is out of the tab order, so ` +
604
+ 'focus could not land there. Assert isDisabled() instead.');
605
+ }
606
+ return staticDoc;
607
+ }
529
608
  /** The surface, when a user could change its text. */
530
609
  async writableSurface(method) {
531
610
  const surface = await this.focusableSurface(method);
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-editor-testing.mjs","sources":["../../../projects/lib/editor/testing/wr-editor-harness.ts","../../../projects/lib/editor/testing/ngwr-editor-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n ComponentHarness,\n HarnessPredicate,\n type ModifierKeys,\n type TestElement,\n type TestKey,\n parallel,\n} from '@angular/cdk/testing';\n\nimport { WrButtonHarness } from 'ngwr/button/testing';\nimport { WrInputHarness } from 'ngwr/input/testing';\nimport { WrPopoverHarness } from 'ngwr/popover/testing';\n\nimport type { WrEditorHarnessFilters } from './interfaces';\n\n/** The text surface ProseMirror mounts on. */\nconst SURFACE = '.wr-editor__surface';\n\n/** One toolbar button. The separators are `<span>`s and never match. */\nconst TOOL = '.wr-editor__tool';\n\n/**\n * The link tool — the one button that hosts a popover rather than a tooltip, and so\n * the one announcing `aria-haspopup=\"dialog\"`.\n */\nconst LINK_TOOL = `${TOOL}[aria-haspopup=\"dialog\"]`;\n\n/**\n * Every textblock the schema can draw. A list item, a quote and a table cell all hold\n * paragraphs, so this reaches every run of text however deeply it is nested — and\n * nothing else: a task item's box and its screen-reader words sit OUTSIDE the\n * paragraph, beside it.\n */\nconst TEXTBLOCKS = `${SURFACE} :is(p, h1, h2, h3, pre)`;\n\n/**\n * The modifier ProseMirror's keymap reads `Mod` as. It decides from\n * `navigator.platform` itself, once, when `prosemirror-keymap` loads — so this asks\n * the same question the same way rather than guessing from the tooltip, which the\n * component words from the user agent.\n */\nfunction primaryModifier(): ModifierKeys {\n const apple = typeof navigator !== 'undefined' && /Mac|iP(hone|[oa]d)/.test(navigator.platform);\n return apple ? { meta: true } : { control: true };\n}\n\n/**\n * Test harness for `<wr-editor>` — a ProseMirror text surface, the formatting\n * toolbar above it, and the link panel one of its buttons opens.\n *\n * **The value is not read here, and that is the first thing to know.** The model\n * holds an HTML string, a markdown string or a JSON tree depending on `format`, and\n * all three are written by the editor's codec, never by the DOM. What the surface\n * draws is ProseMirror's rendering of the document — a trailing `<br>` in an empty\n * paragraph, a presentational box beside a task item — so a string derived from it\n * would be a fourth format agreeing with none of the three. A spec reads the value\n * from what it bound (`form.body().value()`, the `[(value)]` signal); this harness\n * answers the other question, what is DRAWN ({@link getText}), and a spec on an\n * editor should usually ask both.\n *\n * Four more things are refused, each because jsdom cannot answer it and a method\n * would answer it anyway:\n *\n * - **Typing, character by character.** `sendKeys` dispatches key events and nothing\n * else. In a browser the BROWSER inserts the character into the contenteditable and\n * ProseMirror reads the mutation; jsdom inserts nothing, so a typed string would\n * vanish while the call resolved. {@link setText} writes the surface the way the\n * CDK defines writing a contenteditable, which ProseMirror reads as a real DOM\n * change; {@link paste} inserts at the selection; {@link pressKey} refuses a\n * printable character without a modifier rather than dropping it.\n * - **A caret or a selected range.** Placing either needs a DOM selection inside a\n * text node, which no `TestElement` call can create. {@link selectAll} is the one\n * selection a keyboard makes without a coordinate, and it is the one offered.\n * - **Anything measured** — the height between `--wr-editor-min-height` and\n * `-max-height`, a scroll, how the toolbar wraps, where a tooltip lands. Every box\n * is 0×0 without layout.\n * - **Whether the placeholder is visible.** It is CSS `content` drawn from a data\n * attribute, which no DOM query sees; {@link getPlaceholder} reads what is\n * ANNOUNCED, and that is only there while the document is empty.\n *\n * Toolbar buttons are addressed by the name a screen reader reads — `Bold`,\n * `Heading 2`, `Insert link` in English, the catalog's words otherwise — because the\n * DOM carries nothing else to tell them apart, and that name is the contract anyway.\n * {@link getTool} hands back a `WrButtonHarness`; what that harness does not read\n * (`aria-pressed`, `aria-keyshortcuts`, the roving tab stop) is answered here.\n *\n * Every method reads the MOUNTED surface. ProseMirror takes it over in\n * `afterNextRender`, which the harness's own stabilising lets run; under a server\n * `PLATFORM_ID` it never does, and the static preview the server renders is not\n * something this harness pretends to be the editor.\n *\n * **A spec under jsdom installs one stub first.** After an edit made while the\n * surface has focus, ProseMirror scrolls the selection into view, which measures a\n * `Range` — and jsdom's `Range` has no `getClientRects()`. The throw comes AFTER the\n * document changed but before the model hears of it, from inside a DOM listener, so\n * the model keeps the old value and the error surfaces only as an unhandled one. Empty\n * boxes are the whole stub; nothing here measures anything, so nothing is lost:\n *\n * ```ts\n * beforeEach(() => {\n * Range.prototype.getClientRects = () => [] as unknown as DOMRectList;\n * Range.prototype.getBoundingClientRect = () => new DOMRect();\n * document.elementFromPoint = () => null;\n * });\n * // …and delete all three again in afterEach, so no other file inherits them.\n * ```\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const body = await loader.getHarness(WrEditorHarness.with({ label: 'Body' }));\n *\n * await body.setText('Release notes');\n * await body.selectAll();\n * await (await body.getTool('Bold')).click();\n *\n * expect(await body.isToolPressed('Bold')).toBe(true);\n * expect(host.value()).toBe('<p><strong>Release notes</strong></p>');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrEditorHarness extends ComponentHarness {\n static hostSelector = 'wr-editor';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrEditorHarnessFilters = {}): HarnessPredicate<WrEditorHarness> {\n return new HarnessPredicate(WrEditorHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('readonly', options.readonly, async (harness, readonly) => (await harness.isReadonly()) === readonly);\n }\n\n private readonly surface = this.locatorFor(SURFACE);\n\n // ---------------------------------------------------------------------------\n // The surface\n // ---------------------------------------------------------------------------\n\n /**\n * Whether ProseMirror owns the surface yet — read from the `contenteditable` it\n * writes there, which nothing else does.\n *\n * `true` in any browser-platform spec by the time a harness method runs. `false`\n * under a server `PLATFORM_ID`, where the editor is the static preview for good —\n * and every other method here throws, rather than answer about a preview as if it\n * were the editor.\n */\n async isMounted(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('contenteditable')) !== null;\n }\n\n /**\n * The text the surface DRAWS: every textblock (paragraph, heading, code block) in\n * document order, each trimmed, joined with a newline.\n *\n * Joined per textblock rather than read in one go, because a single text read of the\n * surface glues two paragraphs into one word — `Hello` and `world` would come back\n * as `Helloworld`. A list item, a quote and a table cell draw their text in\n * paragraphs too, so a two-item list reads `One\\nTwo`. Left out, deliberately: a task\n * item's `Done:` / `To do:` — screen-reader text beside the paragraph, not in it —\n * and a hard break, which is a `<br>` and has no text. An empty document is `''`.\n *\n * This is the drawn half of the two questions an editor answers; the class docs say\n * why the other half, the value, is read from the model instead.\n */\n async getText(): Promise<string> {\n await this.mountedSurface('getText');\n const blocks = await this.locatorForAll(TEXTBLOCKS)();\n return (await parallel(() => blocks.map(block => block.text()))).join('\\n');\n }\n\n /**\n * The surface's accessible name — `ariaLabel` when one is bound, else the\n * surrounding `<wr-form-field>`'s label, else the catalog's `editor.label`.\n *\n * A field's label reaches the surface through `aria-labelledby`, never `<label for>`:\n * a contenteditable is not labelable. So the reference is resolved the way assistive\n * technology resolves it — each id's element, its text — and a reference to an\n * element that is not on the page contributes nothing, which is the failure worth\n * catching.\n */\n async getLabel(): Promise<string | null> {\n const surface = await this.surface();\n const labelledBy = await surface.getAttribute('aria-labelledby');\n if (labelledBy) {\n const root = this.documentRootLocatorFactory();\n const labels = await parallel(() =>\n labelledBy\n .split(/\\s+/)\n .filter(Boolean)\n .map(async id => {\n const element = await root.locatorForOptional(`[id=\"${id}\"]`)();\n return element ? element.text() : '';\n })\n );\n return labels.filter(Boolean).join(' ') || null;\n }\n return surface.getAttribute('aria-label');\n }\n\n /**\n * The placeholder as announced — `aria-placeholder` — or `null` when there is none\n * to announce.\n *\n * That is `null` whenever the document holds anything, not only when no\n * `placeholder` is bound: the component publishes it only while the document is\n * empty, the moment it is also drawn. A document holding an empty heading still\n * counts as empty.\n */\n async getPlaceholder(): Promise<string | null> {\n return (await this.surface()).getAttribute('aria-placeholder');\n }\n\n /**\n * Whether the editor is disabled — the surface's `aria-disabled`.\n *\n * Read from the surface rather than the `wr-editor--disabled` host class, because a\n * contenteditable has no `disabled` property: the attribute is what a screen reader\n * is told, and ProseMirror's own `contenteditable=\"false\"` is what turns the\n * keyboard away. The component sets both.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-disabled')) === 'true';\n }\n\n /**\n * Whether the editor shows its document and refuses edits — the surface's\n * `aria-readonly`, which `role=\"textbox\"` allows. Read-only keeps the tab stop, so\n * {@link focus} still lands; disabled does not.\n */\n async isReadonly(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-readonly')) === 'true';\n }\n\n /**\n * Whether the surface is announced invalid — the `aria-invalid` the surrounding\n * `<wr-form-field>` hands it when a bound field fails validation and has been\n * touched. An editor outside a field is never invalid.\n */\n async isInvalid(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * Replace the whole document with `text`, as one paragraph of plain text.\n *\n * Written through `TestElement.setContenteditableValue` — the CDK's own definition of\n * writing a contenteditable — which ProseMirror observes as a DOM change and parses\n * back through the schema, exactly as it does the mutation a browser makes for a\n * keystroke. So the write reaches the model through the editor's own path, filters\n * and all. A line break in `text` becomes a hard break inside that one paragraph,\n * the way Shift+Enter makes one; more paragraphs, lists and headings come from the\n * toolbar, {@link pressKey} or {@link pasteHtml}.\n *\n * An empty `text` empties the document, which writes `''` (`null` in `json` format).\n *\n * Throws on a disabled or read-only editor, where no user could change a word — the\n * surface is not contenteditable then, and a write the DOM accepted anyway would be\n * a green spec for something that cannot happen.\n */\n async setText(text: string): Promise<void> {\n const surface = await this.writableSurface('setText');\n await surface.focus();\n await surface.setContenteditableValue(text);\n await this.forceStabilize();\n }\n\n /**\n * Paste `text` as plain text at the selection, replacing whatever is selected — a\n * `paste` event carrying `text/plain`, handled by ProseMirror's own paste path.\n *\n * On a READ-ONLY editor the event is still delivered, because a browser delivers it:\n * the refusal is the editor's to make, and asserting the value did not change is how\n * a spec proves it did. Throws on a disabled one, whose surface cannot hold focus for\n * a paste to arrive at.\n */\n async paste(text: string): Promise<void> {\n await this.dispatchPaste('paste', { 'text/plain': text });\n }\n\n /**\n * Paste `html` at the selection, the way content copied from a web page arrives —\n * `text/html` with `text` as its `text/plain` twin.\n *\n * This is the path to exercise what the editor strips from untrusted markup: the\n * pasted HTML is parsed without executing anything and rebuilt from the schema,\n * exactly as a bound HTML value is, so `onclick`, a `javascript:` link or an\n * `<iframe>` never reaches the document. Read-only and disabled behave as in\n * {@link paste}.\n */\n async pasteHtml(html: string, text = ''): Promise<void> {\n await this.dispatchPaste('pasteHtml', { 'text/html': html, 'text/plain': text });\n }\n\n /**\n * Select the whole document with the platform's select-all chord, as ProseMirror's\n * keymap reads it.\n *\n * Throws on a read-only or disabled editor: ProseMirror hands the keyboard back to\n * the browser once the surface is not editable, and the browser's native select-all\n * is exactly what jsdom does not have, so the chord would select nothing and say so\n * to no one.\n */\n async selectAll(): Promise<void> {\n if (await this.isReadonly()) {\n throw new Error(\n 'WrEditorHarness.selectAll(): the editor is read-only. ProseMirror does not handle the keyboard on a ' +\n 'surface that is not editable — the browser selects natively — and a DOM without layout has no native ' +\n 'selection to make. Assert isReadonly() instead.'\n );\n }\n const surface = await this.writableSurface('selectAll');\n await surface.focus();\n await surface.sendKeys(primaryModifier(), 'a');\n }\n\n /**\n * Press one key on the surface, with modifiers — Enter, Backspace, an arrow, or a\n * shortcut such as `pressKey('b', { control: true })`.\n *\n * The keys reach ProseMirror's keymap, which is where the shortcuts, the list\n * behaviour of Enter and history live. Under jsdom, ProseMirror reads `Mod` as\n * Control; {@link getToolShortcuts} names the chord each tool answers to.\n *\n * A printable character WITHOUT a Control, Meta or Alt modifier throws: inserting it\n * is the browser's job, not the key event's (see the class docs), so it would be\n * dropped while the call resolved — use {@link setText} or {@link paste}. Throws on a\n * disabled editor, whose surface is out of the tab order. A read-only one takes the\n * key and refuses whatever it would have changed, as it does in a browser.\n */\n async pressKey(key: TestKey | string, modifiers: ModifierKeys = {}): Promise<void> {\n if (typeof key === 'string' && !modifiers.control && !modifiers.meta && !modifiers.alt) {\n throw new Error(\n `WrEditorHarness.pressKey(): \"${key}\" would be typed, not pressed. A key event inserts nothing into a ` +\n 'contenteditable — the browser does, and jsdom does not — so the character would be dropped silently. ' +\n 'Use setText() or paste() for text, or pass a modifier for a shortcut.'\n );\n }\n const surface = await this.focusableSurface('pressKey');\n await surface.focus();\n await surface.sendKeys(modifiers, key);\n }\n\n /**\n * Move focus to the text surface. A read-only editor keeps its tab stop and takes\n * it.\n *\n * Throws on a disabled one rather than focusing it. In a browser its surface cannot\n * take focus — no `tabindex`, and `contenteditable=\"false\"` — but jsdom counts ANY\n * `contenteditable` attribute as focusable, `false` included, so the focus would\n * land in the spec and never in the app.\n */\n async focus(): Promise<void> {\n return (await this.focusableSurface('focus')).focus();\n }\n\n /**\n * Take focus away from the surface — the whole control losing focus, which is what\n * emits `touch` and lets a bound field show its error. Focus moving to the editor's\n * own toolbar or link panel is not a blur of the control, and does not emit it.\n */\n async blur(): Promise<void> {\n return (await this.surface()).blur();\n }\n\n /** Whether the text surface holds focus. A toolbar button holding it is not the surface. */\n async isFocused(): Promise<boolean> {\n return (await this.surface()).isFocused();\n }\n\n // ---------------------------------------------------------------------------\n // The toolbar\n // ---------------------------------------------------------------------------\n\n /** Whether a toolbar is drawn — gone with `toolbar=\"false\"`, and with a list holding no tool. */\n async hasToolbar(): Promise<boolean> {\n return (await this.locatorForOptional('[role=\"toolbar\"]')()) !== null;\n }\n\n /** The toolbar's accessible name — the catalog's `editor.toolbar`. Throws when there is no toolbar. */\n async getToolbarLabel(): Promise<string | null> {\n return (await this.toolbar('getToolbarLabel')).getAttribute('aria-label');\n }\n\n /**\n * The names of the drawn tools, in order, separators left out.\n *\n * What is DRAWN, which is not always what was bound: `underline` is dropped in\n * `markdown` format, which cannot store it, and a duplicate or a stray separator is\n * dropped too.\n */\n async getToolLabels(): Promise<string[]> {\n const tools = await this.locatorForAll(TOOL)();\n return (await parallel(() => tools.map(tool => tool.getAttribute('aria-label')))).map(label => label ?? '');\n }\n\n /**\n * The toolbar button named `label`, as a `WrButtonHarness` — click it, focus it, ask\n * whether it is disabled.\n *\n * A tool the current selection cannot use is NATIVELY disabled, so its click never\n * fires; that is the answer to \"can bold apply here\", read with `isDisabled()`. Its\n * tooltip — the name plus the shortcut — is a `[wrPopover]` in tooltip mode, which\n * `WrPopoverHarness` reads when a spec cares about the wording.\n */\n async getTool(label: string | RegExp): Promise<WrButtonHarness> {\n const tool = await this.locatorForOptional(\n WrButtonHarness.with({ selector: TOOL }).add(`label matches ${label}`, async harness =>\n HarnessPredicate.stringMatches((await harness.host()).getAttribute('aria-label'), label)\n )\n )();\n if (tool) return tool;\n\n throw new Error(\n `WrEditorHarness.getTool(): no tool is named ${label}. The toolbar draws ` +\n `${JSON.stringify(await this.getToolLabels())} — note that underline is not drawn in markdown format.`\n );\n }\n\n /**\n * Whether a toggle tool is pressed — its `aria-pressed` — at the current selection.\n *\n * The marks answer for the selection (or, at a caret, for what typing would carry);\n * the block types answer for every textblock the selection touches, so a select-all\n * over a heading and a paragraph presses neither.\n *\n * Throws for a tool that is not a toggle: the link button opens a panel, and undo,\n * redo and the horizontal rule are actions, so none carries `aria-pressed` and a\n * `false` would claim a state they do not have. Whether the selection sits in a link\n * is what the link panel's remove button answers — see {@link removeLink}.\n */\n async isToolPressed(label: string | RegExp): Promise<boolean> {\n const host = await (await this.getTool(label)).host();\n const pressed = await host.getAttribute('aria-pressed');\n if (pressed === null) {\n throw new Error(\n `WrEditorHarness.isToolPressed(): \"${await host.getAttribute('aria-label')}\" is not a toggle, so it carries ` +\n 'no aria-pressed. Link opens a panel; undo, redo and the horizontal rule are actions.'\n );\n }\n return pressed === 'true';\n }\n\n /**\n * The chords a tool answers to, as its `aria-keyshortcuts` states them\n * (`Control+B`, `Control+Shift+Z Control+Y`), or `null` for a tool with none — the\n * horizontal rule.\n *\n * ARIA key names, never localised; `Meta` replaces `Control` where the user agent is\n * Apple's.\n */\n async getToolShortcuts(label: string | RegExp): Promise<string | null> {\n return (await (await this.getTool(label)).host()).getAttribute('aria-keyshortcuts');\n }\n\n /**\n * The name of the one tool in the tab order, or `null` when there is none.\n *\n * The toolbar is a single tab stop that the arrow keys rove. The stop follows the\n * user, and moves off a tool that stopped applying — so after a click that disables\n * undo, it lands on the first tool that still applies. `null` only when nothing does\n * (read-only, disabled), where there is nothing for the keyboard to reach.\n */\n async getToolbarTabStop(): Promise<string | null> {\n const tools = await this.locatorForAll(TOOL)();\n for (const tool of tools) {\n if ((await tool.getAttribute('tabindex')) === '0' && !(await tool.getProperty<boolean>('disabled'))) {\n return tool.getAttribute('aria-label');\n }\n }\n return null;\n }\n\n // ---------------------------------------------------------------------------\n // The link panel\n // ---------------------------------------------------------------------------\n\n /** Whether the link panel is open, from the link tool's `aria-expanded`. `false` when there is no link tool. */\n async isLinkPanelOpen(): Promise<boolean> {\n const tool = await this.locatorForOptional(LINK_TOOL)();\n return !!tool && (await tool.getAttribute('aria-expanded')) === 'true';\n }\n\n /**\n * Open the link panel from its toolbar button and hand back the popover — a\n * content container, so `panel.getHarness(WrInputHarness)` reaches the address\n * field without the spec touching the overlay. An open panel is returned as it is.\n *\n * The panel is a portal in the overlay container, reached through the id the\n * button publishes as `aria-controls`, never by its class: two editors can each have\n * one. Mod-K on the surface opens the same panel — `pressKey('k', { control: true })`.\n *\n * Throws when the toolbar draws no link tool, or when the tool is unavailable (a\n * read-only or disabled editor).\n */\n async openLinkPanel(): Promise<WrPopoverHarness> {\n const panel = await this.linkPopover('openLinkPanel');\n if (await panel.isOpen()) return panel;\n\n if (await (await panel.host()).getProperty<boolean>('disabled')) {\n throw new Error(\n 'WrEditorHarness.openLinkPanel(): the link tool is disabled — nothing can be linked while the editor ' +\n 'is read-only or disabled.'\n );\n }\n await panel.open();\n return panel;\n }\n\n /**\n * Link the selection to `url` through the panel: open it, write the address, apply.\n *\n * With text selected, the selection becomes the link; with the caret inside a link,\n * that link's address changes; with nothing selected at all, the address is inserted\n * as its own linked text. An emptied address on an existing link removes it.\n *\n * Resolves whether or not the address was accepted. One the URL policy refuses — a\n * `javascript:` or `data:` scheme — leaves the panel OPEN with its error showing and\n * the document untouched, which is the behaviour a spec most wants to pin; read it\n * with {@link getLinkError} and {@link isLinkPanelOpen}.\n */\n async setLink(url: string): Promise<void> {\n const panel = await this.openLinkPanel();\n const input = await panel.getHarness(WrInputHarness);\n await input.setValue(url);\n await (await panel.getHarness(WrButtonHarness.with({ selector: '.wr-editor-link__apply' }))).click();\n }\n\n /**\n * Remove the link the selection sits in or covers, through the panel's remove button.\n *\n * Throws when the selection is in no link: the panel draws the button only then, so\n * its absence IS the answer to \"is there a link here\".\n */\n async removeLink(): Promise<void> {\n const panel = await this.openLinkPanel();\n const remove = await panel.getHarnessOrNull(WrButtonHarness.with({ selector: '.wr-editor-link__remove' }));\n if (!remove) {\n throw new Error(\n 'WrEditorHarness.removeLink(): the selection is not in a link, so the panel offers nothing to remove. ' +\n 'Select the linked text first — selectAll() covers a document holding one link.'\n );\n }\n await remove.click();\n }\n\n /**\n * The message the open panel shows for a refused address, or `null` when it shows\n * none. It is a `role=\"alert\"`, and the address field names it with\n * `aria-describedby` — so the text a screen reader hears is the text read here.\n *\n * Throws while the panel is closed: the message only exists inside it, and a `null`\n * would read as \"the address was accepted\".\n */\n async getLinkError(): Promise<string | null> {\n if (!(await this.isLinkPanelOpen())) {\n throw new Error(\n 'WrEditorHarness.getLinkError(): the link panel is closed, so there is no message to read. An accepted ' +\n 'address closes it — isLinkPanelOpen() tells the two outcomes apart.'\n );\n }\n const id = await (await this.locatorFor(LINK_TOOL)()).getAttribute('aria-controls');\n const error = await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"] .wr-editor-link__error`)();\n return error ? error.text() : null;\n }\n\n // ---------------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------------\n\n /** The surface, or a sentence saying the editor is still the server's static preview. */\n private async mountedSurface(method: string): Promise<TestElement> {\n const surface = await this.surface();\n if ((await surface.getAttribute('contenteditable')) === null) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +\n 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the static ' +\n 'preview, and this harness does not answer for it as if it were the editor.'\n );\n }\n return surface;\n }\n\n /** The surface, when a user could still reach it with the keyboard. */\n private async focusableSurface(method: string): Promise<TestElement> {\n const surface = await this.mountedSurface(method);\n if (await this.isDisabled()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is disabled — its surface is out of the tab order, so no key or ` +\n 'paste could arrive there. Assert isDisabled() instead.'\n );\n }\n return surface;\n }\n\n /** The surface, when a user could change its text. */\n private async writableSurface(method: string): Promise<TestElement> {\n const surface = await this.focusableSurface(method);\n if (await this.isReadonly()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is read-only, so no user could change its text — the surface is ` +\n 'not contenteditable. Write through the bound model instead, or assert isReadonly().'\n );\n }\n return surface;\n }\n\n private async dispatchPaste(method: string, data: Readonly<Record<string, string>>): Promise<void> {\n const surface = await this.focusableSurface(method);\n await surface.focus();\n await surface.dispatchEvent('paste', {\n clipboardData: {\n types: Object.keys(data),\n getData: (type: string) => data[type] ?? '',\n },\n });\n }\n\n private async toolbar(method: string): Promise<TestElement> {\n const toolbar = await this.locatorForOptional('[role=\"toolbar\"]')();\n if (toolbar) return toolbar;\n throw new Error(`WrEditorHarness.${method}(): the editor draws no toolbar — toolbar is false, or lists no tool.`);\n }\n\n private async linkPopover(method: string): Promise<WrPopoverHarness> {\n const panel = await this.locatorForOptional(WrPopoverHarness.with({ selector: LINK_TOOL }))();\n if (panel) return panel;\n throw new Error(\n `WrEditorHarness.${method}(): the toolbar draws no link tool. Bind a toolbar that lists 'link' — Mod-K ` +\n 'opens the panel only when the tool is there to host it.'\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-editor-testing.mjs","sources":["../../../projects/lib/editor/testing/wr-editor-harness.ts","../../../projects/lib/editor/testing/ngwr-editor-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport {\n ComponentHarness,\n HarnessPredicate,\n type ModifierKeys,\n type TestElement,\n type TestKey,\n parallel,\n} from '@angular/cdk/testing';\n\nimport { WrButtonHarness } from 'ngwr/button/testing';\nimport { WrInputHarness } from 'ngwr/input/testing';\nimport { WrPopoverHarness } from 'ngwr/popover/testing';\n\nimport type { WrEditorHarnessFilters } from './interfaces';\n\n/** The text surface ProseMirror mounts on. Not rendered at all while the editor is read-only. */\nconst SURFACE = '.wr-editor__surface';\n\n/**\n * The document a read-only editor draws instead of mounting. It exists in that\n * one state and in no other, so finding it IS the answer to \"is this editor\n * read-only\" — the state cannot carry `aria-readonly`, since `role=\"group\"`\n * does not allow one.\n */\nconst STATIC = '.wr-editor__preview--static';\n\n/** Whichever of the two is on screen — the element carrying the editor's id, name and ARIA. */\nconst CONTROL = `:is(${SURFACE}, ${STATIC})`;\n\n/** One toolbar button. The separators are `<span>`s and never match. */\nconst TOOL = '.wr-editor__tool';\n\n/**\n * The link tool — the one button that hosts a popover rather than a tooltip, and so\n * the one announcing `aria-haspopup=\"dialog\"`.\n */\nconst LINK_TOOL = `${TOOL}[aria-haspopup=\"dialog\"]`;\n\n/**\n * Every textblock the schema can draw. A list item, a quote and a table cell all hold\n * paragraphs, so this reaches every run of text however deeply it is nested — and\n * nothing else: a task item's box and its screen-reader words sit OUTSIDE the\n * paragraph, beside it.\n */\nconst TEXTBLOCKS = `${CONTROL} :is(p, h1, h2, h3, pre)`;\n\n/**\n * The modifier ProseMirror's keymap reads `Mod` as. It decides from\n * `navigator.platform` itself, once, when `prosemirror-keymap` loads — so this asks\n * the same question the same way rather than guessing from the tooltip, which the\n * component words from the user agent.\n */\nfunction primaryModifier(): ModifierKeys {\n const apple = typeof navigator !== 'undefined' && /Mac|iP(hone|[oa]d)/.test(navigator.platform);\n return apple ? { meta: true } : { control: true };\n}\n\n/**\n * Test harness for `<wr-editor>` — a ProseMirror text surface, the formatting\n * toolbar above it, and the link panel one of its buttons opens.\n *\n * **The value is not read here, and that is the first thing to know.** The model\n * holds an HTML string, a markdown string or a JSON tree depending on `format`, and\n * all three are written by the editor's codec, never by the DOM. What the surface\n * draws is ProseMirror's rendering of the document — a trailing `<br>` in an empty\n * paragraph, a presentational box beside a task item — so a string derived from it\n * would be a fourth format agreeing with none of the three. A spec reads the value\n * from what it bound (`form.body().value()`, the `[(value)]` signal); this harness\n * answers the other question, what is DRAWN ({@link getText}), and a spec on an\n * editor should usually ask both.\n *\n * Four more things are refused, each because jsdom cannot answer it and a method\n * would answer it anyway:\n *\n * - **Typing, character by character.** `sendKeys` dispatches key events and nothing\n * else. In a browser the BROWSER inserts the character into the contenteditable and\n * ProseMirror reads the mutation; jsdom inserts nothing, so a typed string would\n * vanish while the call resolved. {@link setText} writes the surface the way the\n * CDK defines writing a contenteditable, which ProseMirror reads as a real DOM\n * change; {@link paste} inserts at the selection; {@link pressKey} refuses a\n * printable character without a modifier rather than dropping it.\n * - **A caret or a selected range.** Placing either needs a DOM selection inside a\n * text node, which no `TestElement` call can create. {@link selectAll} is the one\n * selection a keyboard makes without a coordinate, and it is the one offered.\n * - **Anything measured** — the height between `--wr-editor-min-height` and\n * `-max-height`, a scroll, how the toolbar wraps, where a tooltip lands. Every box\n * is 0×0 without layout.\n * - **Whether the placeholder is visible.** It is CSS `content` drawn from a data\n * attribute, which no DOM query sees; {@link getPlaceholder} reads what is\n * ANNOUNCED, and that is only there while the document is empty.\n *\n * Toolbar buttons are addressed by the name a screen reader reads — `Bold`,\n * `Heading 2`, `Insert link` in English, the catalog's words otherwise — because the\n * DOM carries nothing else to tell them apart, and that name is the contract anyway.\n * {@link getTool} hands back a `WrButtonHarness`; what that harness does not read\n * (`aria-pressed`, `aria-keyshortcuts`, the roving tab stop) is answered here.\n *\n * **A read-only editor has no surface at all**, and that is the state to know\n * about before reading anything here. `readonly` set before the editor mounts\n * means ProseMirror is never created: the document is drawn as ordinary markup\n * in a focusable `role=\"group\"`, which takes over the id, the name and the tab\n * stop. So the reads answer from whichever element is on screen —\n * {@link getText}, {@link getLabel}, {@link isDisabled}, {@link isInvalid},\n * {@link focus} — while everything that needs a live view ({@link setText},\n * {@link paste}, {@link pressKey}, {@link selectAll}) throws a sentence saying\n * there is nothing to type into rather than passing on a surface that is not\n * there. {@link isMounted} is the question itself, and {@link isReadonly} keeps\n * answering: the static document exists in that one state, which is how it is\n * read, since ARIA allows no `aria-readonly` on a group.\n *\n * Otherwise every method reads the MOUNTED surface. ProseMirror takes it over in\n * `afterNextRender`, which the harness's own stabilising lets run; under a server\n * `PLATFORM_ID` it never does, and the transient preview the server renders — the\n * one a browser is about to replace — is not something this harness pretends to\n * be the editor.\n *\n * **A spec under jsdom installs one stub first.** After an edit made while the\n * surface has focus, ProseMirror scrolls the selection into view, which measures a\n * `Range` — and jsdom's `Range` has no `getClientRects()`. The throw comes AFTER the\n * document changed but before the model hears of it, from inside a DOM listener, so\n * the model keeps the old value and the error surfaces only as an unhandled one. Empty\n * boxes are the whole stub; nothing here measures anything, so nothing is lost:\n *\n * ```ts\n * beforeEach(() => {\n * Range.prototype.getClientRects = () => [] as unknown as DOMRectList;\n * Range.prototype.getBoundingClientRect = () => new DOMRect();\n * document.elementFromPoint = () => null;\n * });\n * // …and delete all three again in afterEach, so no other file inherits them.\n * ```\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const body = await loader.getHarness(WrEditorHarness.with({ label: 'Body' }));\n *\n * await body.setText('Release notes');\n * await body.selectAll();\n * await (await body.getTool('Bold')).click();\n *\n * expect(await body.isToolPressed('Bold')).toBe(true);\n * expect(host.value()).toBe('<p><strong>Release notes</strong></p>');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrEditorHarness extends ComponentHarness {\n static hostSelector = 'wr-editor';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrEditorHarnessFilters = {}): HarnessPredicate<WrEditorHarness> {\n return new HarnessPredicate(WrEditorHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('readonly', options.readonly, async (harness, readonly) => (await harness.isReadonly()) === readonly);\n }\n\n /** The element carrying the editor's id, name and ARIA — the surface, or the static document. */\n private readonly control = this.locatorFor(CONTROL);\n private readonly liveSurface = this.locatorForOptional(SURFACE);\n private readonly staticDoc = this.locatorForOptional(STATIC);\n\n // ---------------------------------------------------------------------------\n // The surface\n // ---------------------------------------------------------------------------\n\n /**\n * Whether ProseMirror owns a surface yet — read from the `contenteditable` it\n * writes there, which nothing else does.\n *\n * `true` in any browser-platform spec on an editor that is not read-only, by the\n * time a harness method runs. `false` in the two states where no mount is coming:\n * under a server `PLATFORM_ID`, where what shows is the transient preview and\n * every other method here throws rather than answer about it as if it were the\n * editor; and on a READ-ONLY editor, which never mounts by design and whose\n * static document is read here like any other — this is the method that tells\n * those two apart from a mounted one, and {@link isReadonly} tells them apart\n * from each other.\n */\n async isMounted(): Promise<boolean> {\n const surface = await this.liveSurface();\n return !!surface && (await surface.getAttribute('contenteditable')) !== null;\n }\n\n /**\n * The text the surface DRAWS: every textblock (paragraph, heading, code block) in\n * document order, each trimmed, joined with a newline.\n *\n * Joined per textblock rather than read in one go, because a single text read of the\n * surface glues two paragraphs into one word — `Hello` and `world` would come back\n * as `Helloworld`. A list item, a quote and a table cell draw their text in\n * paragraphs too, so a two-item list reads `One\\nTwo`. Left out, deliberately: a task\n * item's `Done:` / `To do:` — screen-reader text beside the paragraph, not in it —\n * and a hard break, which is a `<br>` and has no text. An empty document is `''`.\n *\n * This is the drawn half of the two questions an editor answers; the class docs say\n * why the other half, the value, is read from the model instead.\n *\n * A READ-ONLY editor draws its document without ProseMirror, and this reads that\n * document — it is the editor there, not a stand-in for one.\n */\n async getText(): Promise<string> {\n await this.drawnDocument('getText');\n const blocks = await this.locatorForAll(TEXTBLOCKS)();\n return (await parallel(() => blocks.map(block => block.text()))).join('\\n');\n }\n\n /**\n * The editor's accessible name — `ariaLabel` when one is bound, else the\n * surrounding `<wr-form-field>`'s label, else the catalog's `editor.label`. Read\n * off whichever element carries it: the surface, or a read-only editor's static\n * document, which takes the name over with the tab stop.\n *\n * A field's label reaches the surface through `aria-labelledby`, never `<label for>`:\n * a contenteditable is not labelable. So the reference is resolved the way assistive\n * technology resolves it — each id's element, its text — and a reference to an\n * element that is not on the page contributes nothing, which is the failure worth\n * catching.\n */\n async getLabel(): Promise<string | null> {\n const control = await this.control();\n const labelledBy = await control.getAttribute('aria-labelledby');\n if (labelledBy) {\n const root = this.documentRootLocatorFactory();\n const labels = await parallel(() =>\n labelledBy\n .split(/\\s+/)\n .filter(Boolean)\n .map(async id => {\n const element = await root.locatorForOptional(`[id=\"${id}\"]`)();\n return element ? element.text() : '';\n })\n );\n return labels.filter(Boolean).join(' ') || null;\n }\n return control.getAttribute('aria-label');\n }\n\n /**\n * The placeholder as announced — `aria-placeholder` — or `null` when there is none\n * to announce.\n *\n * That is `null` whenever the document holds anything, not only when no\n * `placeholder` is bound: the component publishes it only while the document is\n * empty, the moment it is also drawn. A document holding an empty heading still\n * counts as empty.\n *\n * Always `null` on a READ-ONLY editor, however `placeholder` is bound, and that is\n * the truth rather than a hole: there is no input to hint at, and\n * `aria-placeholder` is not among the attributes the static document's\n * `role=\"group\"` allows, so the component draws none there either.\n */\n async getPlaceholder(): Promise<string | null> {\n return (await this.control()).getAttribute('aria-placeholder');\n }\n\n /**\n * Whether the editor is disabled — `aria-disabled`, on the surface or on the static\n * document, whichever is drawn.\n *\n * Read from there rather than from the `wr-editor--disabled` host class, because a\n * contenteditable has no `disabled` property: the attribute is what a screen reader\n * is told, and ProseMirror's own `contenteditable=\"false\"` is what turns the\n * keyboard away. The component sets both.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-disabled')) === 'true';\n }\n\n /**\n * Whether the editor shows its document and refuses edits. Read-only keeps the tab\n * stop, so {@link focus} still lands; disabled does not.\n *\n * Two DOM facts answer it, because the two shapes of read-only look nothing alike.\n * An editor that was read-only before it could mount has no surface at all, and the\n * static document it draws instead is drawn in that state and in no other — so its\n * presence is the answer, and it is the only one available: ARIA defines no\n * `aria-readonly` for `role=\"group\"`, and `aria-disabled` would say something else.\n * One that mounted first keeps its surface and reports `aria-readonly` there, which\n * `role=\"textbox\"` does allow.\n */\n async isReadonly(): Promise<boolean> {\n if (await this.staticDoc()) return true;\n return (await (await this.control()).getAttribute('aria-readonly')) === 'true';\n }\n\n /**\n * Whether the editor is announced invalid — the `aria-invalid` the surrounding\n * `<wr-form-field>` hands it when a bound field fails validation and has been\n * touched. An editor outside a field is never invalid.\n */\n async isInvalid(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-invalid')) === 'true';\n }\n\n /**\n * Replace the whole document with `text`, as one paragraph of plain text.\n *\n * Written through `TestElement.setContenteditableValue` — the CDK's own definition of\n * writing a contenteditable — which ProseMirror observes as a DOM change and parses\n * back through the schema, exactly as it does the mutation a browser makes for a\n * keystroke. So the write reaches the model through the editor's own path, filters\n * and all. A line break in `text` becomes a hard break inside that one paragraph,\n * the way Shift+Enter makes one; more paragraphs, lists and headings come from the\n * toolbar, {@link pressKey} or {@link pasteHtml}.\n *\n * An empty `text` empties the document, which writes `''` (`null` in `json` format).\n *\n * Throws on a disabled or read-only editor, where no user could change a word — the\n * surface is not contenteditable then, and is not even rendered when read-only came\n * first, so a write the DOM accepted anyway would be a green spec for something that\n * cannot happen.\n */\n async setText(text: string): Promise<void> {\n const surface = await this.writableSurface('setText');\n await surface.focus();\n await surface.setContenteditableValue(text);\n await this.forceStabilize();\n }\n\n /**\n * Paste `text` as plain text at the selection, replacing whatever is selected — a\n * `paste` event carrying `text/plain`, handled by ProseMirror's own paste path.\n *\n * On an editor that was MOUNTED before it went read-only the event is still\n * delivered, because a browser delivers it: the refusal is the editor's to make, and\n * asserting the value did not change is how a spec proves it did. One that was\n * read-only from the start has no surface for a paste to arrive at, and throws\n * saying so. Throws on a disabled one too, whose surface cannot hold focus.\n */\n async paste(text: string): Promise<void> {\n await this.dispatchPaste('paste', { 'text/plain': text });\n }\n\n /**\n * Paste `html` at the selection, the way content copied from a web page arrives —\n * `text/html` with `text` as its `text/plain` twin.\n *\n * This is the path to exercise what the editor strips from untrusted markup: the\n * pasted HTML is parsed without executing anything and rebuilt from the schema,\n * exactly as a bound HTML value is, so `onclick`, a `javascript:` link or an\n * `<iframe>` never reaches the document. Read-only and disabled behave as in\n * {@link paste}.\n */\n async pasteHtml(html: string, text = ''): Promise<void> {\n await this.dispatchPaste('pasteHtml', { 'text/html': html, 'text/plain': text });\n }\n\n /**\n * Select the whole document with the platform's select-all chord, as ProseMirror's\n * keymap reads it.\n *\n * Throws on a read-only or disabled editor: ProseMirror hands the keyboard back to\n * the browser once the surface is not editable, and the browser's native select-all\n * is exactly what jsdom does not have, so the chord would select nothing and say so\n * to no one — and an editor that was read-only before it could mount has no surface\n * to aim the chord at in the first place.\n */\n async selectAll(): Promise<void> {\n if (await this.isReadonly()) {\n throw new Error(\n 'WrEditorHarness.selectAll(): the editor is read-only. ProseMirror does not handle the keyboard on a ' +\n 'surface that is not editable — the browser selects natively — and a DOM without layout has no native ' +\n 'selection to make. Assert isReadonly() instead.'\n );\n }\n const surface = await this.writableSurface('selectAll');\n await surface.focus();\n await surface.sendKeys(primaryModifier(), 'a');\n }\n\n /**\n * Press one key on the surface, with modifiers — Enter, Backspace, an arrow, or a\n * shortcut such as `pressKey('b', { control: true })`.\n *\n * The keys reach ProseMirror's keymap, which is where the shortcuts, the list\n * behaviour of Enter and history live. Under jsdom, ProseMirror reads `Mod` as\n * Control; {@link getToolShortcuts} names the chord each tool answers to.\n *\n * A printable character WITHOUT a Control, Meta or Alt modifier throws: inserting it\n * is the browser's job, not the key event's (see the class docs), so it would be\n * dropped while the call resolved — use {@link setText} or {@link paste}. Throws on a\n * disabled editor, whose surface is out of the tab order. A read-only one takes the\n * key and refuses whatever it would have changed, as it does in a browser.\n */\n async pressKey(key: TestKey | string, modifiers: ModifierKeys = {}): Promise<void> {\n if (typeof key === 'string' && !modifiers.control && !modifiers.meta && !modifiers.alt) {\n throw new Error(\n `WrEditorHarness.pressKey(): \"${key}\" would be typed, not pressed. A key event inserts nothing into a ` +\n 'contenteditable — the browser does, and jsdom does not — so the character would be dropped silently. ' +\n 'Use setText() or paste() for text, or pass a modifier for a shortcut.'\n );\n }\n const surface = await this.focusableSurface('pressKey');\n await surface.focus();\n await surface.sendKeys(modifiers, key);\n }\n\n /**\n * Move focus to the text — the mounted surface, or a read-only editor's static\n * document, which keeps a tab stop for exactly this.\n *\n * Throws on a disabled editor rather than focusing it. In a browser its surface\n * cannot take focus — no `tabindex`, and `contenteditable=\"false\"` — but jsdom\n * counts ANY `contenteditable` attribute as focusable, `false` included, so the\n * focus would land in the spec and never in the app.\n */\n async focus(): Promise<void> {\n return (await this.focusableControl('focus')).focus();\n }\n\n /**\n * Take focus away from the text — the whole control losing focus, which is what\n * emits `touch` and lets a bound field show its error. Focus moving to the editor's\n * own toolbar or link panel is not a blur of the control, and does not emit it.\n */\n async blur(): Promise<void> {\n return (await this.control()).blur();\n }\n\n /** Whether the text holds focus. A toolbar button holding it is not the text. */\n async isFocused(): Promise<boolean> {\n return (await this.control()).isFocused();\n }\n\n // ---------------------------------------------------------------------------\n // The toolbar\n // ---------------------------------------------------------------------------\n\n /** Whether a toolbar is drawn — gone with `toolbar=\"false\"`, and with a list holding no tool. */\n async hasToolbar(): Promise<boolean> {\n return (await this.locatorForOptional('[role=\"toolbar\"]')()) !== null;\n }\n\n /** The toolbar's accessible name — the catalog's `editor.toolbar`. Throws when there is no toolbar. */\n async getToolbarLabel(): Promise<string | null> {\n return (await this.toolbar('getToolbarLabel')).getAttribute('aria-label');\n }\n\n /**\n * The names of the drawn tools, in order, separators left out.\n *\n * What is DRAWN, which is not always what was bound: `underline` is dropped in\n * `markdown` format, which cannot store it, and a duplicate or a stray separator is\n * dropped too.\n */\n async getToolLabels(): Promise<string[]> {\n const tools = await this.locatorForAll(TOOL)();\n return (await parallel(() => tools.map(tool => tool.getAttribute('aria-label')))).map(label => label ?? '');\n }\n\n /**\n * The toolbar button named `label`, as a `WrButtonHarness` — click it, focus it, ask\n * whether it is disabled.\n *\n * A tool the current selection cannot use is NATIVELY disabled, so its click never\n * fires; that is the answer to \"can bold apply here\", read with `isDisabled()`. Its\n * tooltip — the name plus the shortcut — is a `[wrPopover]` in tooltip mode, which\n * `WrPopoverHarness` reads when a spec cares about the wording.\n */\n async getTool(label: string | RegExp): Promise<WrButtonHarness> {\n const tool = await this.locatorForOptional(\n WrButtonHarness.with({ selector: TOOL }).add(`label matches ${label}`, async harness =>\n HarnessPredicate.stringMatches((await harness.host()).getAttribute('aria-label'), label)\n )\n )();\n if (tool) return tool;\n\n throw new Error(\n `WrEditorHarness.getTool(): no tool is named ${label}. The toolbar draws ` +\n `${JSON.stringify(await this.getToolLabels())} — note that underline is not drawn in markdown format.`\n );\n }\n\n /**\n * Whether a toggle tool is pressed — its `aria-pressed` — at the current selection.\n *\n * The marks answer for the selection (or, at a caret, for what typing would carry);\n * the block types answer for every textblock the selection touches, so a select-all\n * over a heading and a paragraph presses neither.\n *\n * Throws for a tool that is not a toggle: the link button opens a panel, and undo,\n * redo and the horizontal rule are actions, so none carries `aria-pressed` and a\n * `false` would claim a state they do not have. Whether the selection sits in a link\n * is what the link panel's remove button answers — see {@link removeLink}.\n */\n async isToolPressed(label: string | RegExp): Promise<boolean> {\n const host = await (await this.getTool(label)).host();\n const pressed = await host.getAttribute('aria-pressed');\n if (pressed === null) {\n throw new Error(\n `WrEditorHarness.isToolPressed(): \"${await host.getAttribute('aria-label')}\" is not a toggle, so it carries ` +\n 'no aria-pressed. Link opens a panel; undo, redo and the horizontal rule are actions.'\n );\n }\n return pressed === 'true';\n }\n\n /**\n * The chords a tool answers to, as its `aria-keyshortcuts` states them\n * (`Control+B`, `Control+Shift+Z Control+Y`), or `null` for a tool with none — the\n * horizontal rule.\n *\n * ARIA key names, never localised; `Meta` replaces `Control` where the user agent is\n * Apple's.\n */\n async getToolShortcuts(label: string | RegExp): Promise<string | null> {\n return (await (await this.getTool(label)).host()).getAttribute('aria-keyshortcuts');\n }\n\n /**\n * The name of the one tool in the tab order, or `null` when there is none.\n *\n * The toolbar is a single tab stop that the arrow keys rove. The stop follows the\n * user, and moves off a tool that stopped applying — so after a click that disables\n * undo, it lands on the first tool that still applies. `null` only when nothing does\n * (read-only, disabled), where there is nothing for the keyboard to reach.\n */\n async getToolbarTabStop(): Promise<string | null> {\n const tools = await this.locatorForAll(TOOL)();\n for (const tool of tools) {\n if ((await tool.getAttribute('tabindex')) === '0' && !(await tool.getProperty<boolean>('disabled'))) {\n return tool.getAttribute('aria-label');\n }\n }\n return null;\n }\n\n // ---------------------------------------------------------------------------\n // The link panel\n // ---------------------------------------------------------------------------\n\n /** Whether the link panel is open, from the link tool's `aria-expanded`. `false` when there is no link tool. */\n async isLinkPanelOpen(): Promise<boolean> {\n const tool = await this.locatorForOptional(LINK_TOOL)();\n return !!tool && (await tool.getAttribute('aria-expanded')) === 'true';\n }\n\n /**\n * Open the link panel from its toolbar button and hand back the popover — a\n * content container, so `panel.getHarness(WrInputHarness)` reaches the address\n * field without the spec touching the overlay. An open panel is returned as it is.\n *\n * The panel is a portal in the overlay container, reached through the id the\n * button publishes as `aria-controls`, never by its class: two editors can each have\n * one. Mod-K on the surface opens the same panel — `pressKey('k', { control: true })`.\n *\n * Throws when the toolbar draws no link tool, or when the tool is unavailable (a\n * read-only or disabled editor).\n */\n async openLinkPanel(): Promise<WrPopoverHarness> {\n const panel = await this.linkPopover('openLinkPanel');\n if (await panel.isOpen()) return panel;\n\n if (await (await panel.host()).getProperty<boolean>('disabled')) {\n throw new Error(\n 'WrEditorHarness.openLinkPanel(): the link tool is disabled — nothing can be linked while the editor ' +\n 'is read-only or disabled.'\n );\n }\n await panel.open();\n return panel;\n }\n\n /**\n * Link the selection to `url` through the panel: open it, write the address, apply.\n *\n * With text selected, the selection becomes the link; with the caret inside a link,\n * that link's address changes; with nothing selected at all, the address is inserted\n * as its own linked text. An emptied address on an existing link removes it.\n *\n * Resolves whether or not the address was accepted. One the URL policy refuses — a\n * `javascript:` or `data:` scheme — leaves the panel OPEN with its error showing and\n * the document untouched, which is the behaviour a spec most wants to pin; read it\n * with {@link getLinkError} and {@link isLinkPanelOpen}.\n */\n async setLink(url: string): Promise<void> {\n const panel = await this.openLinkPanel();\n const input = await panel.getHarness(WrInputHarness);\n await input.setValue(url);\n await (await panel.getHarness(WrButtonHarness.with({ selector: '.wr-editor-link__apply' }))).click();\n }\n\n /**\n * Remove the link the selection sits in or covers, through the panel's remove button.\n *\n * Throws when the selection is in no link: the panel draws the button only then, so\n * its absence IS the answer to \"is there a link here\".\n */\n async removeLink(): Promise<void> {\n const panel = await this.openLinkPanel();\n const remove = await panel.getHarnessOrNull(WrButtonHarness.with({ selector: '.wr-editor-link__remove' }));\n if (!remove) {\n throw new Error(\n 'WrEditorHarness.removeLink(): the selection is not in a link, so the panel offers nothing to remove. ' +\n 'Select the linked text first — selectAll() covers a document holding one link.'\n );\n }\n await remove.click();\n }\n\n /**\n * The message the open panel shows for a refused address, or `null` when it shows\n * none. It is a `role=\"alert\"`, and the address field names it with\n * `aria-describedby` — so the text a screen reader hears is the text read here.\n *\n * Throws while the panel is closed: the message only exists inside it, and a `null`\n * would read as \"the address was accepted\".\n */\n async getLinkError(): Promise<string | null> {\n if (!(await this.isLinkPanelOpen())) {\n throw new Error(\n 'WrEditorHarness.getLinkError(): the link panel is closed, so there is no message to read. An accepted ' +\n 'address closes it — isLinkPanelOpen() tells the two outcomes apart.'\n );\n }\n const id = await (await this.locatorFor(LINK_TOOL)()).getAttribute('aria-controls');\n const error = await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"] .wr-editor-link__error`)();\n return error ? error.text() : null;\n }\n\n // ---------------------------------------------------------------------------\n // Internals\n // ---------------------------------------------------------------------------\n\n /** The element drawing the editor's own document — the mounted surface, or the static one. */\n private async drawnDocument(method: string): Promise<TestElement> {\n if ((await this.isMounted()) || (await this.staticDoc())) return this.control();\n throw new Error(\n `WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +\n 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the transient ' +\n 'preview, and this harness does not answer for it as if it were the editor. A read-only editor is the ' +\n 'other way round: it never mounts by design, and the document it draws IS the editor, so it reads here ' +\n 'like any other.'\n );\n }\n\n /** The surface, or a sentence saying what is on screen instead of one. */\n private async mountedSurface(method: string): Promise<TestElement> {\n const surface = await this.liveSurface();\n if (surface && (await surface.getAttribute('contenteditable')) !== null) return surface;\n if (await this.staticDoc()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is read-only, so ProseMirror was never mounted and there is no ` +\n 'contenteditable surface for a key, a paste or a write to arrive at. Assert isReadonly(), read the ' +\n 'document with getText(), or lift readonly first — the editor mounts then.'\n );\n }\n throw new Error(\n `WrEditorHarness.${method}(): the editor is not mounted. ProseMirror takes the surface over in ` +\n 'afterNextRender, which never runs under a server PLATFORM_ID — what shows there is the transient ' +\n 'preview, and this harness does not answer for it as if it were the editor.'\n );\n }\n\n /** The surface, when a user could still reach it with the keyboard. */\n private async focusableSurface(method: string): Promise<TestElement> {\n const surface = await this.mountedSurface(method);\n if (await this.isDisabled()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is disabled — its surface is out of the tab order, so no key or ` +\n 'paste could arrive there. Assert isDisabled() instead.'\n );\n }\n return surface;\n }\n\n /** Whatever a keyboard user could put focus on: the surface, or the static document. */\n private async focusableControl(method: string): Promise<TestElement> {\n const staticDoc = await this.staticDoc();\n if (!staticDoc) return this.focusableSurface(method);\n if (await this.isDisabled()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is disabled — the document it draws is out of the tab order, so ` +\n 'focus could not land there. Assert isDisabled() instead.'\n );\n }\n return staticDoc;\n }\n\n /** The surface, when a user could change its text. */\n private async writableSurface(method: string): Promise<TestElement> {\n const surface = await this.focusableSurface(method);\n if (await this.isReadonly()) {\n throw new Error(\n `WrEditorHarness.${method}(): the editor is read-only, so no user could change its text — the surface is ` +\n 'not contenteditable. Write through the bound model instead, or assert isReadonly().'\n );\n }\n return surface;\n }\n\n private async dispatchPaste(method: string, data: Readonly<Record<string, string>>): Promise<void> {\n const surface = await this.focusableSurface(method);\n await surface.focus();\n await surface.dispatchEvent('paste', {\n clipboardData: {\n types: Object.keys(data),\n getData: (type: string) => data[type] ?? '',\n },\n });\n }\n\n private async toolbar(method: string): Promise<TestElement> {\n const toolbar = await this.locatorForOptional('[role=\"toolbar\"]')();\n if (toolbar) return toolbar;\n throw new Error(`WrEditorHarness.${method}(): the editor draws no toolbar — toolbar is false, or lists no tool.`);\n }\n\n private async linkPopover(method: string): Promise<WrPopoverHarness> {\n const panel = await this.locatorForOptional(WrPopoverHarness.with({ selector: LINK_TOOL }))();\n if (panel) return panel;\n throw new Error(\n `WrEditorHarness.${method}(): the toolbar draws no link tool. Bind a toolbar that lists 'link' — Mod-K ` +\n 'opens the panel only when the tool is there to host it.'\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}