pseudo-dom 0.11.0 → 0.12.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.
package/README.md CHANGED
@@ -1,99 +1,173 @@
1
1
  # Pseudo DOM
2
2
 
3
- Mock the DOM for server side-side DOM state and in tests.
3
+ Mock the DOM for server-side DOM state and in tests.
4
4
 
5
5
  ## Status: early development (0.x)
6
6
 
7
- Pseudo DOM recreates the browser DOM API (following the MDN documentation) so DOM-dependent code can run in Node, for
8
- example in tests, without a real or headless browser. It is written in TypeScript and ships type definitions.
9
-
10
- Working today: `EventService` (Event) and `EventTargetService` (EventTarget), which dispatch an event through the tree the
11
- way the DOM does (capture down from the root, the target, then bubbling back up, with `stopPropagation`,
12
- `stopImmediatePropagation`, `once`, `passive`, `preventDefault` and the default action of the target; clicking a submit
13
- button sends a `submit` event to its form), `NodeService`
14
- (Node: a real tree with `parentNode`, `previousSibling` / `nextSibling`, `firstChild` / `lastChild`, `appendChild`,
15
- `insertBefore`, `removeChild`, `replaceChild`, `contains` and `getRootNode`; nodes move when they are added somewhere
16
- else, document fragments insert their children, and a node cannot be put inside itself), `ElementService` /
17
- `HTMLElementService` (Element and HTMLElement, including attributes, `NamedNodeMap`, `classList`), `AttrService`,
18
- `DOMTokenListService`, `NamedNodeMapService`, `DocumentService` / `DocumentFragmentService`, `PseudoNodeList`, and
19
- `generateDocument` for creating a document.
20
-
21
- `style` is a real, live `CSSStyleDeclaration`: named property access (\`el.style.backgroundColor = 'red'\`) alongside
22
- \`getPropertyValue\` / \`setProperty\` / \`removeProperty\` / \`getPropertyPriority\` / \`cssText\`; an unset property
23
- reads as \`''\`, like the DOM's. \`dataset\` is a live `DOMStringMap`: reading/writing/deleting a camelCase name
24
- (\`el.dataset.fooBar\`) reads/writes/removes the matching \`data-foo-bar\` attribute directly, so it can never fall out
25
- of sync with the attributes themselves. Both are on \`HTMLElementService\`, matching the DOM (they are not on the base
26
- Element).
27
-
28
- Standard events: `createEvent(type, init, { browser, trusted })` makes the kind of event which suits the type (a click is a
29
- `MouseEvent`, a keydown a `KeyboardEvent`, ...). Like the constructor in a browser it gives nothing (no bubbling, no
30
- cancelling, not trusted) unless asked, but with `browser: true` it uses `eventDefaults`, the table of how the browser
31
- creates each standard type (click bubbles and can be cancelled, focus does not bubble but focusin does, input bubbles
32
- but cannot be cancelled, ...), and `trusted: true` makes `isTrusted` true, as for a real user action. The kinds of event
33
- are `PseudoUIEvent`, `PseudoMouseEvent`, `PseudoPointerEvent`, `PseudoKeyboardEvent`, `PseudoFocusEvent`,
34
- `PseudoInputEvent` and `PseudoCustomEvent`. Elements have `click()` (an untrusted click, like a script's),
35
- `focus()` and `blur()` (with blur / focusout / focus / focusin and the related targets, and a focused element for each
36
- tree), and `simulate.click(element)` / `simulate.keyPress(element, key)` send what a user's action sends (pointerdown,
37
- mousedown, the focus moving, pointerup, mouseup, click; keydown, keyup), all trusted.
38
-
39
- Nodes can be copied and compared: `cloneNode(deep)` copies an element with its attributes (objects such as `style` are
40
- copied too, not shared) and, when deep, everything below it, without the parent or the event listeners; `isEqualNode`
41
- compares two nodes by what they hold (tag, attributes in any order, text and children in order);
42
- `compareDocumentPosition` says where another node is (`NodeService.DOCUMENT_POSITION_*`); `isConnected` is true when the
43
- tree has a document at the top and `ownerDocument` says which one made the node; `normalize` joins neighbouring text
44
- nodes. There are text and comment nodes (`PseudoText`, `PseudoComment`) and `textContent` works like the DOM's (the text
45
- of everything below, and setting it replaces the children with a text node); the document makes them with
46
- `createTextNode`, `createComment` and `createDocumentFragment`.
47
-
48
- Elements can be walked and changed like the DOM: \`children\` is a live \`HTMLCollection\` of just the element
49
- children, \`childElementCount\`, \`firstElementChild\` / \`lastElementChild\` and \`nextElementSibling\` /
50
- \`previousElementSibling\` skip text and comment nodes. \`append\` / \`prepend\` / \`before\` / \`after\` / \`remove\` /
51
- \`replaceWith\` / \`replaceChildren\` accept nodes or strings (a string becomes a text node) and move a node already in
52
- a tree rather than duplicating it; \`insertAdjacentElement\` / \`insertAdjacentText\` / \`insertAdjacentHTML\` insert at
53
- beforebegin / afterbegin / beforeend / afterend.
54
-
55
- \`innerHTML\` / \`outerHTML\` parse and serialize real HTML, via [htmlparser2](https://www.npmjs.com/package/htmlparser2)
56
- (a SAX-style tokenizer; pseudo-dom builds its own nodes from its events, the same way it builds a tree from
57
- \`css-select\`'s selector matches) - entities decode, void elements (\`br\`, \`img\`, ...) auto-close, and \`class\` /
58
- \`style\` attributes populate \`className\`/\`classList\` and \`style\` for real, not just a generic attribute. The
59
- setters (\`innerHTML =\`, \`outerHTML =\`) and \`insertAdjacentHTML\` are on \`HTMLElementService\` (they need to build
60
- real \`HTMLElement\`s); \`ElementService\` only has the getters (serializing is fine without them).
61
-
62
- Selector queries work like the DOM's: \`getElementsByTagName\` / \`getElementsByClassName\` (live, on any node) and
63
- \`querySelector\` / \`querySelectorAll\` (real CSS selectors, via [css-select](https://www.npmjs.com/package/css-select)
64
- matched against pseudo-dom's own tree through a custom adapter - \`querySelectorAll\` is a plain array, a snapshot
65
- taken when it is called, like the DOM's) are on \`NodeService\` so \`Document\`, \`DocumentFragment\` and \`Element\`
66
- all have them; \`matches\` / \`closest\` are on \`ElementService\`; \`getElementById\` is on both \`DocumentService\`
67
- and \`DocumentFragmentService\` (the DOM's \`NonElementParentNode\` mixin, so a \`ShadowRoot\` gets it too), matching
68
- the real DOM.
69
-
70
- \`DocumentService\` matches the real \`Document\` (it is not an \`Element\`, so it has no \`tagName\` / \`classList\` /
71
- \`matches\` / etc.): \`createElement\`, \`createTextNode\`, \`createComment\`, \`createDocumentFragment\`,
72
- \`getElementById\`, and \`textContent\` always \`null\`. \`PseudoHTMLDocument\` (what \`generateDocument\` actually
73
- creates) only adds the \`html\` / \`head\` / \`body\` structure on top.
74
-
75
- Attribute helpers work like the DOM's: \`getAttributeNames\`, \`hasAttributes\`, \`toggleAttribute(name, [force])\`;
76
- \`localName\` matches \`tagName\` and \`prefix\` is always null (there is no real namespace parsing);
77
- \`getElementsByTagNameNS\` behaves exactly like \`getElementsByTagName\`, ignoring the namespace.
78
-
79
- \`attachShadow({mode})\` attaches a real (\`DocumentFragmentService\`-based) \`ShadowRoot\`, with \`host\` and \`mode\`
80
- set; \`element.shadowRoot\` reaches it when the mode is 'open', like the DOM's (a 'closed' one still exists, just not
81
- this way); attaching a second one throws.
82
-
83
- There is no layout engine, so anything that would need one is settable directly rather than really computed - set the
84
- value a test needs and the getter/method returns it: \`clientWidth\` / \`clientHeight\` / \`clientTop\` /
85
- \`clientLeft\` / \`scrollWidth\` / \`scrollHeight\` (numbers, alongside the existing \`offsetWidth\` etc.),
86
- \`boundingClientRect\` (what \`getBoundingClientRect()\` returns), \`clientRects\` (\`getClientRects()\`),
87
- \`animations\` (\`getAnimations()\`) and \`isVisible\` (\`checkVisibility()\`). \`scrollLeft\` / \`scrollTop\` are real,
88
- plain settable numbers, and \`scroll\` / \`scrollTo\` / \`scrollBy\` (a number pair or an options object) update them
89
- for real; \`scrollIntoView\` is a real callable no-op (there is no viewport to scroll within).
90
- \`hasPointerCapture\` / \`setPointerCapture\` / \`releasePointerCapture\` genuinely track capture per pointer id.
91
- \`requestFullscreen\` / \`requestPointerLock\` resolve, like a browser granting the request would.
92
- \`computedStyleMap()\` is a thin read-only view of the element's own inline style (there is no CSS cascade).
93
-
94
- Not implemented yet (these throw a "not implemented" error or are missing): the \`aria*\` reflected properties, and
95
- the \`Attr\`-node / namespaced attribute methods (\`getAttributeNode\`, \`getAttributeNS\`, ...). The API will change
96
- before 1.0.
7
+ Pseudo DOM recreates the browser DOM API (following the MDN documentation) so DOM-dependent code can run in Node -
8
+ in tests, without a real or headless browser. It's written in TypeScript and ships type definitions. The API will
9
+ change before 1.0.
10
+
11
+ ## At a glance
12
+
13
+ - **Tree & events** - a real node tree with full event dispatch (capture → target → bubble) and standard event
14
+ defaults for clicks, focus and more
15
+ - **Elements** - attributes, `classList`, a real live `style` and `dataset`, `children`, and DOM-style mutation
16
+ (`append`, `before`, `remove`, `replaceWith`, ...)
17
+ - **HTML parsing** - `innerHTML` / `outerHTML` / `insertAdjacentHTML` parse and serialize real HTML
18
+ - **Queries** - `querySelector` / `querySelectorAll` with real CSS selectors, `getElementById`, `matches`, `closest`
19
+ - **Document** - `createElement`, `createTextNode`, and `generateDocument()` to get a `window`-like object with
20
+ `document` already on it
21
+ - **Cloning & comparison** - `cloneNode`, `isEqualNode`, `compareDocumentPosition`, `contains`
22
+ - **Running headlessly** - `installGlobal()` makes code written against real DOM globals (`document.createElement`,
23
+ ...) run unmodified in Node; `logElement()` prints a readable, indented view of an element to the console
24
+
25
+ Everything below goes into more detail, section by section.
26
+
27
+ ## Tree, nodes & events
28
+
29
+ A real tree: `parentNode`, `previousSibling` / `nextSibling`, `firstChild` / `lastChild`, `appendChild`,
30
+ `insertBefore`, `removeChild`, `replaceChild`, `contains`, `getRootNode`. Nodes move when they're added somewhere
31
+ else, document fragments insert their children instead of themselves, and a node can't be put inside itself.
32
+
33
+ `EventService` / `EventTargetService` dispatch an event through the tree the way the DOM does:
34
+
35
+ - Capture down from the root, hit the target, then bubble back up
36
+ - `stopPropagation`, `stopImmediatePropagation`, `once`, `passive`, `preventDefault`, and the target's default action
37
+ (clicking a submit button sends a `submit` event to its form)
38
+ - `createEvent(type, init, { browser, trusted })` makes the kind of event that suits the type (a click is a
39
+ `MouseEvent`, a keydown a `KeyboardEvent`, ...). Like a real constructor, it gives nothing (no bubbling, no
40
+ cancelling, not trusted) unless asked - but `browser: true` applies `eventDefaults`, the table of how a browser
41
+ creates each standard type (click bubbles and can be cancelled; focus doesn't bubble but focusin does; input
42
+ bubbles but can't be cancelled; ...), and `trusted: true` makes `isTrusted` true, as for a real user action
43
+ - The event kinds: `PseudoUIEvent`, `PseudoMouseEvent`, `PseudoPointerEvent`, `PseudoKeyboardEvent`,
44
+ `PseudoFocusEvent`, `PseudoInputEvent`, `PseudoCustomEvent`
45
+ - Elements have `click()` (an untrusted click, like a script's) and `focus()` / `blur()` (blur/focusout/focus/focusin
46
+ and the related targets, with a focused element tracked per tree)
47
+ - `simulate.click(element)` / `simulate.keyPress(element, key)` send what a real user action sends - pointerdown,
48
+ mousedown, the focus moving, pointerup, mouseup, click; keydown, keyup - all trusted
49
+
50
+ ## Elements
51
+
52
+ `ElementService` / `HTMLElementService` cover `Element` and `HTMLElement`: attributes, `NamedNodeMap`, `classList`.
53
+ `AttrService`, `DOMTokenListService` and `NamedNodeMapService` back them.
54
+
55
+ **Attribute helpers** work like the DOM's: `getAttributeNames`, `hasAttributes`, `toggleAttribute(name, [force])`.
56
+ `localName` matches `tagName` and `prefix` is always `null` (there's no real namespace parsing);
57
+ `getElementsByTagNameNS` behaves exactly like `getElementsByTagName`, ignoring the namespace.
58
+
59
+ **`style`** is a real, live `CSSStyleDeclaration`:
60
+
61
+ - Named property access (`el.style.backgroundColor = 'red'`) alongside `getPropertyValue` / `setProperty` /
62
+ `removeProperty` / `getPropertyPriority` / `cssText`
63
+ - An unset property reads as `''`, like the DOM's
64
+
65
+ **`dataset`** is a live `DOMStringMap`: reading/writing/deleting a camelCase name (`el.dataset.fooBar`)
66
+ reads/writes/removes the matching `data-foo-bar` attribute directly, so it can never fall out of sync with the
67
+ attributes themselves.
68
+
69
+ *(`style` and `dataset` are both on `HTMLElementService`, matching the DOM - they're not on the base `Element`.)*
70
+
71
+ **Traversal & mutation** work like the DOM's:
72
+
73
+ - `children` is a live `HTMLCollection` of just the element children; `childElementCount`, `firstElementChild` /
74
+ `lastElementChild` and `nextElementSibling` / `previousElementSibling` skip text and comment nodes
75
+ - `append` / `prepend` / `before` / `after` / `remove` / `replaceWith` / `replaceChildren` accept nodes or strings (a
76
+ string becomes a text node), and move a node already in a tree rather than duplicating it
77
+ - `insertAdjacentElement` / `insertAdjacentText` / `insertAdjacentHTML` insert at beforebegin / afterbegin /
78
+ beforeend / afterend
79
+
80
+ **`attachShadow({mode})`** attaches a real (`DocumentFragmentService`-based) `ShadowRoot`, with `host` and `mode`
81
+ set. `element.shadowRoot` reaches it when the mode is `'open'`, like the DOM's (a `'closed'` one still exists, just
82
+ not reachable this way); attaching a second one throws.
83
+
84
+ ### HTML parsing
85
+
86
+ `innerHTML` / `outerHTML` / `insertAdjacentHTML` parse and serialize real HTML, via
87
+ [htmlparser2](https://www.npmjs.com/package/htmlparser2) (a SAX-style tokenizer - pseudo-dom builds its own nodes
88
+ from its events, the same way it builds matches from `css-select`).
89
+
90
+ - Entities decode, and void elements (`br`, `img`, ...) auto-close
91
+ - `class` / `style` attributes populate `className` / `classList` and `style` for real, not just a generic attribute
92
+ - The setters (`innerHTML =`, `outerHTML =`) and `insertAdjacentHTML` are on `HTMLElementService` (building new
93
+ elements needs a concrete element class); `ElementService` only has the getters, since serializing doesn't
94
+
95
+ ### Layout (mocked, not computed)
96
+
97
+ There's no layout engine, so anything that would need one is **settable directly** rather than really computed - set
98
+ the value a test needs, and the getter/method returns it:
99
+
100
+ - `clientWidth` / `clientHeight` / `clientTop` / `clientLeft` / `scrollWidth` / `scrollHeight` (plain numbers,
101
+ alongside the existing `offsetWidth` etc.)
102
+ - `boundingClientRect` backs `getBoundingClientRect()`, `clientRects` backs `getClientRects()`, `animations` backs
103
+ `getAnimations()`, `isVisible` backs `checkVisibility()`
104
+
105
+ A few things in this group are genuinely real, not mocked:
106
+
107
+ - `scrollLeft` / `scrollTop` are plain settable numbers, and `scroll` / `scrollTo` / `scrollBy` (a number pair or an
108
+ options object) update them for real; `scrollIntoView` is a callable no-op (there's no viewport to scroll within)
109
+ - `hasPointerCapture` / `setPointerCapture` / `releasePointerCapture` genuinely track capture per pointer id
110
+ - `requestFullscreen` / `requestPointerLock` resolve, like a browser granting the request would
111
+ - `computedStyleMap()` is a thin read-only view of the element's own inline style (there's no CSS cascade)
112
+
113
+ ## Queries
114
+
115
+ `getElementsByTagName` / `getElementsByClassName` (live, on any node) and `querySelector` / `querySelectorAll` (real
116
+ CSS selectors, via [css-select](https://www.npmjs.com/package/css-select) matched against pseudo-dom's own tree
117
+ through a custom adapter) are on `NodeService`, so `Document`, `DocumentFragment` and `Element` all have them.
118
+ `querySelectorAll` returns a plain array - a snapshot taken when it's called, like the DOM's.
119
+
120
+ `matches` / `closest` are on `ElementService`. `getElementById` is on both `DocumentService` and
121
+ `DocumentFragmentService` (the DOM's `NonElementParentNode` mixin, so a `ShadowRoot` gets it too).
122
+
123
+ ## Document
124
+
125
+ `DocumentService` matches the real `Document` - it's not an `Element`, so it has no `tagName` / `classList` /
126
+ `matches` / etc:
127
+
128
+ - `createElement`, `createTextNode`, `createComment`, `createDocumentFragment`
129
+ - `getElementById`
130
+ - `textContent` is always `null`
131
+
132
+ `DocumentFragmentService` is the plain `DocumentFragment`. `PseudoHTMLDocument` - what `generateDocument()` actually
133
+ creates - only adds the `html` / `head` / `body` structure on top of `DocumentService`. `PseudoNodeList` backs
134
+ `childNodes`.
135
+
136
+ ## Running headlessly
137
+
138
+ Code written against a real DOM's globals (`document.createElement`, `new Node()`, ...) can run unmodified in Node,
139
+ without an `if (typeof document === 'undefined')` check at every call site:
140
+
141
+ ```js
142
+ if (typeof document === 'undefined') {
143
+ require('pseudo-dom').installGlobal(globalThis)
144
+ }
145
+ ```
146
+
147
+ `installGlobal(target = globalThis)` fills in `document` / `Node` / `Element` / `HTMLElement` on `target`, using
148
+ [browser-or-node](https://www.npmjs.com/package/browser-or-node) to check whether a real DOM (a real browser, or a
149
+ jsdom-based test environment) is already there first - it's always safe to call, everywhere: it does nothing when
150
+ one is.
151
+
152
+ There's no real rendering to look at, so `logElement(node, [label])` prints a readable, indented view of a node's
153
+ markup to the console (`prettyPrint(node, [indent])`, from the same module, returns the string instead of printing
154
+ it) - useful for watching pseudo-dom-driven code run, or checking a final result, from a terminal.
155
+
156
+ ## Cloning, comparison & connection
157
+
158
+ - `cloneNode(deep)` copies an element with its attributes (objects such as `style` are copied too, not shared) and,
159
+ when deep, everything below it - without the parent or the event listeners
160
+ - `isEqualNode` compares two nodes by what they hold (tag, attributes in any order, text and children in order)
161
+ - `compareDocumentPosition` says where another node is (`NodeService.DOCUMENT_POSITION_*`)
162
+ - `isConnected` is true when the tree has a document at the top; `ownerDocument` says which one made the node
163
+ - `normalize` joins neighbouring text nodes
164
+ - There are text and comment nodes (`PseudoText`, `PseudoComment`); `textContent` works like the DOM's (the text of
165
+ everything below, and setting it replaces the children with a text node)
166
+
167
+ ## Not implemented yet
168
+
169
+ - The `aria*` reflected properties
170
+ - The `Attr`-node / namespaced attribute methods (`getAttributeNode`, `getAttributeNS`, ...)
97
171
  ## Modules
98
172
 
99
173
  <dl>
@@ -255,6 +329,10 @@ mock properties left out, style added from the live CSSStyleDeclaration when it
255
329
  <dt><a href="#serializeNode">serializeNode(node)</a> ⇒ <code>string</code></dt>
256
330
  <dd><p>One node, serialized (its own markup only - see serializeChildren for its descendants too).</p>
257
331
  </dd>
332
+ <dt><a href="#prettyPrintNode">prettyPrintNode(node, depth, indent)</a> ⇒ <code>string</code></dt>
333
+ <dd><p>One node, indented for readability (see prettyPrint) - unlike serializeNode, every non-empty node is its own
334
+ line, so the structure of a whole tree is easy to read at a glance.</p>
335
+ </dd>
258
336
  <dt><a href="#generateNodeList">generateNodeList([innerList])</a> ⇒ <code><a href="#PseudoNodeList">PseudoNodeList</a></code></dt>
259
337
  <dd><p>Create a PseudoNodeList, optionally starting from an existing chain of linkers.</p>
260
338
  </dd>
@@ -5447,6 +5525,20 @@ One node, serialized (its own markup only - see serializeChildren for its descen
5447
5525
  | --- | --- |
5448
5526
  | node | <code>\*</code> |
5449
5527
 
5528
+ <a name="prettyPrintNode"></a>
5529
+
5530
+ ## prettyPrintNode(node, depth, indent) ⇒ <code>string</code>
5531
+ One node, indented for readability (see prettyPrint) - unlike serializeNode, every non-empty node is its own
5532
+ line, so the structure of a whole tree is easy to read at a glance.
5533
+
5534
+ **Kind**: global function
5535
+
5536
+ | Param | Type |
5537
+ | --- | --- |
5538
+ | node | <code>\*</code> |
5539
+ | depth | <code>number</code> |
5540
+ | indent | <code>string</code> |
5541
+
5450
5542
  <a name="generateNodeList"></a>
5451
5543
 
5452
5544
  ## generateNodeList([innerList]) ⇒ [<code>PseudoNodeList</code>](#PseudoNodeList)