• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tbranyen / diffhtml / 12189631411

05 Dec 2024 11:09PM CUT coverage: 98.581%. Remained the same
12189631411

Pull #349

github

web-flow
Merge 3ee60590a into f25d1c4ac
Pull Request #349: Bump path-to-regexp and express in /packages/diffhtml-static-sync

840 of 896 branches covered (93.75%)

4795 of 4864 relevant lines covered (98.58%)

427.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.64
/packages/diffhtml/lib/node/patch.js
1
/**
2✔
2
 * @typedef {import('../util/types').ValidNode} ValidNode
2✔
3
 * @typedef {import('../util/types').VTree} VTree
2✔
4
 * @typedef {import('../util/types').TransactionState} TransactionState
2✔
5
 */
2✔
6
import createNode from './create';
2✔
7
import { protectVTree, unprotectVTree } from '../util/memory';
2✔
8
import decodeEntities from '../util/decode-entities';
2✔
9
import { PATCH_TYPE, EMPTY, NodeCache } from '../util/types';
2✔
10
import { $$insertAfter } from '../util/symbols';
2✔
11

2✔
12
const { keys } = Object;
2✔
13
const blocklist = new Set();
2✔
14
const allowlist = new Set();
2✔
15

2✔
16
/**
2✔
17
 * Sets an attribute on an element.
2✔
18
 *
2✔
19
 * @param {VTree} vTree
2✔
20
 * @param {ValidNode} domNode
2✔
21
 * @param {string} name
2✔
22
 * @param {any} value
2✔
23
 *
2✔
24
 * @return {void}
2✔
25
 */
2✔
26
const setAttribute = (vTree, domNode, name, value) => {
2✔
27
  const isObject = typeof value === 'object' && value;
75✔
28
  const isFunction = typeof value === 'function';
75✔
29
  const isSymbol = typeof value === 'symbol';
75✔
30
  const isEvent = name.indexOf('on') === 0;
75✔
31
  const anyNode = /** @type {any} */ (domNode);
75✔
32

75✔
33
  // Events must be lowercased otherwise they will not be set correctly.
75✔
34
  const lowerName = isEvent ? name.toLowerCase() : name;
75✔
35

75✔
36
  // Runtime checking if the property can be set.
75✔
37
  const blocklistName = 's-' + vTree.nodeName + '-' + lowerName;
75✔
38

75✔
39
  /** @type {HTMLElement} */
75✔
40
  const htmlElement = /** @type {any} */ (domNode);
75✔
41

75✔
42
  // Since this is a property value it gets set directly on the node.
75✔
43
  if (allowlist.has(blocklistName)) {
75✔
44
    anyNode[lowerName] = value;
30✔
45
  }
30✔
46
  else if (!blocklist.has(blocklistName)) {
45✔
47
    try {
42✔
48
      anyNode[lowerName] = value;
42✔
49
      allowlist.add(blocklistName);
42✔
50
    }
42✔
51
    catch {
42✔
52
      blocklist.add(blocklistName);
1✔
53
    }
1✔
54
  }
42✔
55

75✔
56
  // If the value is not an object, function, or symbol, then attempt to
75✔
57
  // set as an attribute. If the value is one of the excluded types, they
75✔
58
  // will be set below.
75✔
59
  if (!isObject && !isFunction && !isSymbol) {
75✔
60
    // For boolean/empty attributes, do not try and set a value, just an empty
65✔
61
    // string.
65✔
62
    const emptyValue = value === null || value === undefined || value === true;
65✔
63
    htmlElement.setAttribute(lowerName, emptyValue ? EMPTY.STR : value);
65✔
64
  }
65✔
65
  // Support patching an object representation of the style object.
10✔
66
  else if (isObject && lowerName === 'style') {
10✔
67
    const valueKeys = /** @type {any} */ (keys(value));
1✔
68

1✔
69
    for (let i = 0; i < valueKeys.length; i++) {
1✔
70
      htmlElement.style[valueKeys[i]] = value[valueKeys[i]];
1✔
71
    }
1✔
72
  }
1✔
73
};
2✔
74

2✔
75
/**
2✔
76
 * Removes an attribute from an element.
2✔
77
 *
2✔
78
 * @param {VTree} vTree
2✔
79
 * @param {ValidNode} domNode
2✔
80
 * @param {string} name
2✔
81
 * @return {void}
2✔
82
 */
2✔
83
const removeAttribute = (vTree, domNode, name) => {
2✔
84
  // Runtime checking if the property can be set.
16✔
85
  const blocklistName = 'r-' + vTree.nodeName + '-' + name;
16✔
86
  const anyNode = /** @type {any} */ (domNode);
16✔
87

16✔
88
  if (allowlist.has(blocklistName)) {
16✔
89
    anyNode[name] = undefined;
5✔
90
    delete anyNode[name];
5✔
91
  }
5✔
92
  else if (!blocklist.has(blocklistName)) {
11✔
93
    try {
9✔
94
      anyNode[name] = undefined;
9✔
95
      delete anyNode[name];
9✔
96
      allowlist.add(blocklistName);
9✔
97
    }
9✔
98
    catch {
9✔
99
      blocklist.add(blocklistName);
2✔
100
    }
2✔
101
  }
9✔
102

16✔
103
  /** @type {HTMLElement} */ (domNode).removeAttribute(name);
16✔
104
};
2✔
105

2✔
106
/**
2✔
107
 *
2✔
108
 * @param {*} patches
2✔
109
 * @param {TransactionState=} state
2✔
110
 */
2✔
111
export default function patchNode(patches, state = EMPTY.OBJ) {
2✔
112
  const { ownerDocument, svgElements = new Set() } = state;
197✔
113
  const { length } = patches;
197✔
114

197✔
115
  let i = 0;
197✔
116

197✔
117
  while (true) {
197✔
118
    const patchType = patches[i];
601✔
119

601✔
120
    if (i === length) {
601✔
121
      break;
197✔
122
    }
197✔
123

404✔
124
    switch(patchType) {
404✔
125
      case PATCH_TYPE.REMOVE_ATTRIBUTE:
601✔
126
      case PATCH_TYPE.SET_ATTRIBUTE: {
601✔
127
        const isSet = patchType === PATCH_TYPE.SET_ATTRIBUTE;
91✔
128
        const vTree = patches[i + 1];
91✔
129
        const name = patches[i + 2];
91✔
130
        const value = isSet ? decodeEntities(patches[i + 3]) : null;
91✔
131

91✔
132
        i += isSet ? 4 : 3;
91✔
133

91✔
134
        const isSVG = svgElements.has(vTree);
91✔
135
        const domNode = /** @type {HTMLElement} */ (
91✔
136
          createNode(vTree, ownerDocument, isSVG)
91✔
137
        );
91✔
138

91✔
139
        protectVTree(vTree);
91✔
140

91✔
141
        const setOrRemove = isSet ? setAttribute : removeAttribute;
91✔
142

91✔
143
        setOrRemove(vTree, domNode, name, value);
91✔
144

91✔
145
        break;
91✔
146
      }
91✔
147

601✔
148
      case PATCH_TYPE.NODE_VALUE: {
601✔
149
        const vTree = patches[i + 1];
115✔
150
        const nodeValue = patches[i + 2];
115✔
151
        const isSVG = svgElements.has(vTree);
115✔
152

115✔
153
        i += 4;
115✔
154

115✔
155
        const domNode = /** @type {Text} */ (
115✔
156
          createNode(vTree, ownerDocument, isSVG)
115✔
157
        );
115✔
158

115✔
159
        protectVTree(vTree);
115✔
160

115✔
161
        if (nodeValue.includes('&')) {
115✔
162
          domNode.nodeValue = decodeEntities(nodeValue);
4✔
163
        }
4✔
164
        else {
111✔
165
          domNode.nodeValue = nodeValue;
111✔
166
        }
111✔
167

115✔
168
        break;
115✔
169
      }
115✔
170

601✔
171
      case PATCH_TYPE.INSERT_BEFORE: {
601✔
172
        const vTree = patches[i + 1];
107✔
173
        const newTree = patches[i + 2];
107✔
174
        let refTree = patches[i + 3];
107✔
175

107✔
176
        i += 4;
107✔
177

107✔
178
        if (!NodeCache.has(vTree) && vTree !== $$insertAfter) {
107✔
179
          continue;
1✔
180
        }
1✔
181

106✔
182
        // First attempt to locate a pre-existing DOM Node. If one hasn't been
106✔
183
        // created there could be a few reasons.
106✔
184
        let domNode = /** @type {HTMLElement} */ (
106✔
185
          NodeCache.get(vTree)
106✔
186
        );
106✔
187

106✔
188
        // This allows turning insertBefore into insertAfter. It is only used
106✔
189
        // for Components, but could be used externally as well.
106✔
190
        if (vTree === $$insertAfter) {
107!
191
          const refNode = NodeCache.get(refTree);
×
192

×
193
          // Try and find the parentNode if it exists.
×
194
          if (refNode) {
×
195
            domNode = refNode.parentNode;
×
196

×
197
            refTree = refNode.nextSibling ? refNode.nextSibling : null;
×
198
          }
×
199
        }
×
200

106✔
201
        const isSVG = svgElements.has(newTree);
106✔
202

106✔
203
        protectVTree(newTree);
106✔
204

106✔
205
        const refNode = refTree && /** @type {HTMLElement} */ (
107✔
206
          createNode(refTree, ownerDocument, isSVG)
1✔
207
        );
107✔
208

107✔
209
        const newNode = /** @type {HTMLElement} */ (
107✔
210
          createNode(newTree, ownerDocument, isSVG)
107✔
211
        );
107✔
212

107✔
213
        // If refNode is `undefined` then it will simply append like
107✔
214
        // `appendChild`.
107✔
215
        domNode.insertBefore(newNode, refNode || null);
107✔
216

107✔
217
        break;
107✔
218
      }
107✔
219

601✔
220
      case PATCH_TYPE.REPLACE_CHILD: {
601✔
221
        const newTree = patches[i + 1];
64✔
222
        const oldTree = patches[i + 2];
64✔
223

64✔
224
        i += 3;
64✔
225

64✔
226
        const isSVG = svgElements.has(newTree);
64✔
227

64✔
228
        const oldDomNode = /** @type {HTMLElement} */ (NodeCache.get(oldTree));
64✔
229
        const newDomNode = /** @type {HTMLElement} */ (
64✔
230
          createNode(newTree, ownerDocument, isSVG)
64✔
231
        );
64✔
232

64✔
233
        // Patching without an existing DOM Node is a mistake, so we should not
64✔
234
        // attempt to do anything in this case.
64✔
235
        if (!oldDomNode || !oldDomNode.parentNode) {
64✔
236
          break;
1✔
237
        }
1✔
238

63✔
239
        // Ensure the `newTree` is protected before any DOM operations occur.
63✔
240
        // This is due to the `connectedCallback` in Web Components firing off,
63✔
241
        // and possibly causing a `gc()` to wipe this out.
63✔
242
        protectVTree(newTree);
63✔
243
        oldDomNode.parentNode.insertBefore(newDomNode, oldDomNode);
63✔
244
        oldDomNode.parentNode.removeChild(oldDomNode);
63✔
245
        unprotectVTree(oldTree);
63✔
246

63✔
247
        break;
63✔
248
      }
63✔
249

601✔
250
      case PATCH_TYPE.REMOVE_CHILD: {
601✔
251
        const vTree = patches[i + 1];
27✔
252

27✔
253
        i += 2;
27✔
254

27✔
255
        const domNode = /** @type {HTMLElement} */ (NodeCache.get(vTree));
27✔
256

27✔
257
        if (!domNode || !domNode.parentNode) {
27✔
258
          break;
1✔
259
        }
1✔
260

26✔
261
        domNode.parentNode.removeChild(domNode);
26✔
262
        unprotectVTree(vTree);
26✔
263

26✔
264
        break;
26✔
265
      }
26✔
266
    }
601✔
267
  }
601✔
268
}
197✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc