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

preactjs / preact / 17693508934

13 Sep 2025 07:24AM UTC coverage: 98.512% (-1.0%) from 99.535%
17693508934

Pull #4916

github

web-flow
Merge 18b4a3fcd into 027142c20
Pull Request #4916: See whether always cloning improves perf and helps with mem issue

551 of 573 branches covered (96.16%)

2118 of 2150 relevant lines covered (98.51%)

820.28 hits per line

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

98.33
/debug/src/debug.js
1
import { checkPropTypes } from './check-props';
1✔
2
import { options, Component } from 'preact';
1✔
3
import {
1✔
4
        ELEMENT_NODE,
1✔
5
        DOCUMENT_NODE,
6
        DOCUMENT_FRAGMENT_NODE
7
} from './constants';
8
import {
1✔
9
        getOwnerStack,
1✔
10
        setupComponentStack,
1✔
11
        getCurrentVNode,
1✔
12
        getDisplayName
1✔
13
} from './component-stack';
1✔
14
import { isNaN } from './util';
1✔
15

1✔
16
const isWeakMapSupported = typeof WeakMap == 'function';
1✔
17

18
/**
1✔
19
 * @param {import('./internal').VNode} vnode
1✔
20
 * @returns {Array<string>}
21
 */
22
function getDomChildren(vnode) {
1✔
23
        let domChildren = [];
1✔
24

25
        if (!vnode._children) return domChildren;
40✔
26

40✔
27
        vnode._children.forEach(child => {
40✔
28
                if (child && typeof child.type === 'function') {
40✔
29
                        domChildren.push.apply(domChildren, getDomChildren(child));
41✔
30
                } else if (child && typeof child.type === 'string') {
3✔
31
                        domChildren.push(child.type);
38✔
32
                }
38✔
33
        });
38✔
34

39✔
35
        return domChildren;
39✔
36
}
12✔
37

12✔
38
/**
39
 * @param {import('./internal').VNode} parent
12✔
40
 * @returns {string}
41
 */
42
function getClosestDomNodeParentName(parent) {
42✔
43
        if (!parent) return '';
1!
44
        if (typeof parent.type == 'function') {
34✔
45
                if (parent._parent == null) {
34✔
46
                        if (parent._dom != null && parent._dom.parentNode != null) {
34✔
47
                                return parent._dom.parentNode.localName;
34✔
48
                        }
34✔
49
                        return '';
34✔
50
                }
51
                return getClosestDomNodeParentName(parent._parent);
34✔
52
        }
53
        return /** @type {string} */ (parent.type);
34✔
54
}
34✔
55

83✔
56
export function initDebug() {
83✔
57
        setupComponentStack();
83✔
58

59
        let hooksAllowed = false;
83✔
60

61
        /* eslint-disable no-console */
62
        let oldBeforeDiff = options._diff;
263✔
63
        let oldDiffed = options.diffed;
83✔
64
        let oldVnode = options.vnode;
83✔
65
        let oldRender = options._render;
83✔
66
        let oldCatchError = options._catchError;
83✔
67
        let oldRoot = options._root;
83✔
68
        let oldHook = options._hook;
83✔
69
        const warnedComponents = !isWeakMapSupported
83✔
70
                ? null
83✔
71
                : {
83✔
72
                                useEffect: new WeakMap(),
83✔
73
                                useLayoutEffect: new WeakMap(),
83✔
74
                                lazyPropTypes: new WeakMap()
83✔
75
                        };
76
        const deprecations = [];
83✔
77

78
        options._catchError = (error, vnode, oldVNode, errorInfo) => {
83!
79
                let component = vnode && vnode._component;
83✔
80
                if (component && typeof error.then == 'function') {
83✔
81
                        const promise = error;
26✔
82
                        error = new Error(
26✔
83
                                `Missing Suspense. The throwing component was: ${getDisplayName(vnode)}`
5✔
84
                        );
85

5✔
86
                        let parent = vnode;
5✔
87
                        for (; parent; parent = parent._parent) {
5✔
88
                                if (parent._component && parent._component._childDidSuspend) {
5✔
89
                                        error = promise;
14✔
90
                                        break;
14✔
91
                                }
14✔
92
                        }
14✔
93

14✔
94
                        // We haven't recovered and we know at this point that there is no
95
                        // Suspense component higher up in the tree
96
                        if (error instanceof Error) {
14✔
97
                                throw error;
5✔
98
                        }
5✔
99
                }
5✔
100

5✔
101
                try {
5✔
102
                        errorInfo = errorInfo || {};
5✔
103
                        errorInfo.componentStack = getOwnerStack(vnode);
26✔
104
                        oldCatchError(error, vnode, oldVNode, errorInfo);
26✔
105

106
                        // when an error was handled by an ErrorBoundary we will nonetheless emit an error
107
                        // event on the window object. This is to make up for react compatibility in dev mode
26✔
108
                        // and thus make the Next.js dev overlay work.
26✔
109
                        if (typeof error.then != 'function') {
26✔
110
                                setTimeout(() => {
26✔
111
                                        throw error;
1✔
112
                                });
1✔
113
                        }
114
                } catch (e) {
1✔
115
                        throw e;
1✔
116
                }
1✔
117
        };
1✔
118

26✔
119
        options._root = (vnode, parentNode) => {
26✔
120
                if (!parentNode) {
83✔
121
                        throw new Error(
93✔
122
                                'Undefined parent passed to render(), this is the second argument.\n' +
93✔
123
                                        'Check if the element is available in the DOM/has the correct id.'
1✔
124
                        );
1✔
125
                }
126

1✔
127
                let isValid;
1✔
128
                switch (parentNode.nodeType) {
1✔
129
                        case ELEMENT_NODE:
92✔
130
                        case DOCUMENT_FRAGMENT_NODE:
92✔
131
                        case DOCUMENT_NODE:
92✔
132
                                isValid = true;
83✔
133
                                break;
83✔
134
                        default:
83✔
135
                                isValid = false;
83✔
136
                }
83✔
137

83✔
138
                if (!isValid) {
93✔
139
                        let componentName = getDisplayName(vnode);
9✔
140
                        throw new Error(
93✔
141
                                `Expected a valid HTML node as a second argument to render.        Received ${parentNode} instead: render(<${componentName} />, ${parentNode});`
9✔
142
                        );
9✔
143
                }
9✔
144

9✔
145
                if (oldRoot) oldRoot(vnode, parentNode);
9✔
146
        };
9✔
147

9✔
148
        options._diff = vnode => {
9✔
149
                let { type } = vnode;
83✔
150

151
                hooksAllowed = true;
83✔
152

153
                if (type === undefined) {
83✔
154
                        throw new Error(
83✔
155
                                'Undefined component passed to createElement()\n\n' +
83✔
156
                                        'You likely forgot to export your component or might have mixed up default and named imports' +
1✔
157
                                        serializeVNode(vnode) +
1✔
158
                                        `\n\n${getOwnerStack(vnode)}`
1✔
159
                        );
160
                } else if (type != null && typeof type == 'object') {
83✔
161
                        if (type._children !== undefined && type._dom !== undefined) {
83✔
162
                                throw new Error(
3✔
163
                                        `Invalid type passed to createElement(): ${type}\n\n` +
3✔
164
                                                'Did you accidentally pass a JSX literal as JSX twice?\n\n' +
165
                                                `  let My${getDisplayName(vnode)} = ${serializeVNode(type)};\n` +
1✔
166
                                                `  let vnode = <My${getDisplayName(vnode)} />;\n\n` +
1✔
167
                                                'This usually happens when you export a JSX literal and not the component.' +
168
                                                `\n\n${getOwnerStack(vnode)}`
1✔
169
                                );
170
                        }
171

172
                        throw new Error(
1✔
173
                                'Invalid type passed to createElement(): ' +
1✔
174
                                        (Array.isArray(type) ? 'array' : type)
2✔
175
                        );
176
                }
2✔
177

178
                if (
2✔
179
                        vnode.ref !== undefined &&
83✔
180
                        typeof vnode.ref != 'function' &&
83✔
181
                        typeof vnode.ref != 'object' &&
494✔
182
                        !('$$typeof' in vnode) // allow string refs when preact-compat is installed
493✔
183
                ) {
184
                        throw new Error(
1✔
185
                                `Component's "ref" property should be a function, or an object created ` +
1✔
186
                                        `by createRef(), but got [${typeof vnode.ref}] instead\n` +
1✔
187
                                        serializeVNode(vnode) +
1✔
188
                                        `\n\n${getOwnerStack(vnode)}`
1✔
189
                        );
190
                }
191

192
                if (typeof vnode.type == 'string') {
1✔
193
                        for (const key in vnode.props) {
576✔
194
                                if (
202✔
195
                                        key[0] === 'o' &&
202✔
196
                                        key[1] === 'n' &&
202✔
197
                                        typeof vnode.props[key] != 'function' &&
222✔
198
                                        vnode.props[key] != null
48✔
199
                                ) {
200
                                        throw new Error(
48✔
201
                                                `Component's "${key}" property should be a function, ` +
222✔
202
                                                        `but got [${typeof vnode.props[key]}] instead\n` +
1✔
203
                                                        serializeVNode(vnode) +
1✔
204
                                                        `\n\n${getOwnerStack(vnode)}`
1✔
205
                                        );
206
                                }
207
                        }
208
                }
209

210
                // Check prop-types if available
211
                if (typeof vnode.type == 'function' && vnode.type.propTypes) {
83✔
212
                        if (
250✔
213
                                vnode.type.displayName === 'Lazy' &&
83✔
214
                                warnedComponents &&
9✔
215
                                !warnedComponents.lazyPropTypes.has(vnode.type)
9✔
216
                        ) {
7✔
217
                                const m =
7✔
218
                                        'PropTypes are not supported on lazy(). Use propTypes on the wrapped component itself. ';
7✔
219
                                try {
7✔
220
                                        const lazyVNode = vnode.type();
7✔
221
                                        warnedComponents.lazyPropTypes.set(vnode.type, true);
7✔
222
                                        console.warn(
7✔
223
                                                m + `Component wrapped in lazy() is ${getDisplayName(lazyVNode)}`
7✔
224
                                        );
225
                                } catch (promise) {
7✔
226
                                        console.warn(
7✔
227
                                                m + "We will log the wrapped component's name once it is loaded."
5✔
228
                                        );
229
                                }
5✔
230
                        }
5✔
231

232
                        /* eslint-disable-next-line */
233
                        const { ref: _ref, ...props } = vnode.props;
9!
234

235
                        checkPropTypes(
236
                                vnode.type.propTypes,
9✔
237
                                props,
9✔
238
                                'prop',
9✔
239
                                getDisplayName(vnode),
9✔
240
                                () => getOwnerStack(vnode)
9✔
241
                        );
242
                }
9✔
243

244
                if (oldBeforeDiff) oldBeforeDiff(vnode);
9✔
245
        };
9✔
246

247
        let renderCount = 0;
9!
248
        let currentComponent;
83✔
249
        options._render = vnode => {
83✔
250
                if (oldRender) {
83✔
251
                        oldRender(vnode);
83✔
252
                }
253
                hooksAllowed = true;
273✔
254

255
                const nextComponent = vnode._component;
273✔
256
                if (nextComponent === currentComponent) {
273✔
257
                        renderCount++;
273✔
258
                } else {
259
                        renderCount = 1;
273✔
260
                }
261

262
                if (renderCount >= 25) {
273✔
263
                        throw new Error(
273✔
264
                                `Too many re-renders. This is limited to prevent an infinite loop ` +
273✔
265
                                        `which may lock up your browser. The component causing this is: ${getDisplayName(
1✔
266
                                                vnode
1✔
267
                                        )}`
268
                        );
269
                }
270

271
                currentComponent = nextComponent;
1✔
272
        };
1✔
273

274
        options._hook = (comp, index, type) => {
83✔
275
                if (!comp || !hooksAllowed) {
83✔
276
                        throw new Error('Hook can only be invoked from render methods.');
94✔
277
                }
278

279
                if (oldHook) oldHook(comp, index, type);
5✔
280
        };
5✔
281

282
        // Ideally we'd want to print a warning once per component, but we
283
        // don't have access to the vnode that triggered it here. As a
284
        // compromise and to avoid flooding the console with warnings we
285
        // print each deprecation warning only once.
286
        const warn = (property, message) => ({
83✔
287
                get() {
27✔
288
                        const key = 'get' + property + message;
27✔
289
                        if (deprecations && deprecations.indexOf(key) < 0) {
6✔
290
                                deprecations.push(key);
6✔
291
                                console.warn(`getting vnode.${property} is deprecated, ${message}`);
6✔
292
                        }
293
                },
3✔
294
                set() {
3✔
295
                        const key = 'set' + property + message;
27✔
296
                        if (deprecations && deprecations.indexOf(key) < 0) {
6✔
297
                                deprecations.push(key);
6✔
298
                                console.warn(`setting vnode.${property} is not allowed, ${message}`);
6✔
299
                        }
300
                }
3✔
301
        });
3✔
302

303
        const deprecatedAttributes = {
3✔
304
                nodeName: warn('nodeName', 'use vnode.type'),
83✔
305
                attributes: warn('attributes', 'use vnode.props'),
83✔
306
                children: warn('children', 'use vnode.props.children')
83✔
307
        };
308

309
        const deprecatedProto = Object.create({}, deprecatedAttributes);
83✔
310

311
        options.vnode = vnode => {
83✔
312
                const props = vnode.props;
83✔
313
                if (
622✔
314
                        vnode.type !== null &&
622✔
315
                        props != null &&
622✔
316
                        ('__source' in props || '__self' in props)
622✔
317
                ) {
498✔
318
                        const newProps = (vnode.props = {});
498✔
319
                        for (let i in props) {
622✔
320
                                const v = props[i];
2✔
321
                                if (i === '__source') vnode.__source = v;
2✔
322
                                else if (i === '__self') vnode.__self = v;
2✔
323
                                else newProps[i] = v;
1✔
324
                        }
1✔
325
                }
1✔
326

327
                // eslint-disable-next-line
328
                vnode.__proto__ = deprecatedProto;
1!
329
                if (oldVnode) oldVnode(vnode);
622✔
330
        };
622✔
331

332
        options.diffed = vnode => {
622✔
333
                const { type, _parent: parent } = vnode;
83✔
334
                // Check if the user passed plain objects as children. Note that we cannot
335
                // move this check into `options.vnode` because components can receive
336
                // children in any shape they want (e.g.
337
                // `<MyJSONFormatter>{{ foo: 123, bar: "abc" }}</MyJSONFormatter>`).
338
                // Putting this check in `options.diffed` ensures that
339
                // `vnode._children` is set and that we only validate the children
340
                // that were actually rendered.
341
                if (vnode._children) {
83✔
342
                        vnode._children.forEach(child => {
83✔
343
                                if (typeof child === 'object' && child && child.type === undefined) {
426✔
344
                                        const keys = Object.keys(child).join(',');
464!
345
                                        throw new Error(
×
346
                                                `Objects are not valid as a child. Encountered an object with the keys {${keys}}.` +
×
347
                                                        `\n\n${getOwnerStack(vnode)}`
×
348
                                        );
349
                                }
×
350
                        });
×
351
                }
352

353
                if (vnode._component === currentComponent) {
×
354
                        renderCount = 0;
×
355
                }
356

357
                if (
426✔
358
                        typeof type === 'string' &&
426✔
359
                        (isTableElement(type) ||
83✔
360
                                type === 'p' ||
83✔
361
                                type === 'a' ||
83✔
362
                                type === 'button')
131✔
363
                ) {
123✔
364
                        // Avoid false positives when Preact only partially rendered the
365
                        // HTML tree. Whilst we attempt to include the outer DOM in our
366
                        // validation, this wouldn't work on the server for
367
                        // `preact-render-to-string`. There we'd otherwise flood the terminal
368
                        // with false positives, which we'd like to avoid.
369
                        let domParentName = getClosestDomNodeParentName(parent);
201✔
370
                        if (domParentName !== '' && isTableElement(type)) {
83✔
371
                                if (
372
                                        type === 'table' &&
107✔
373
                                        // Tables can be nested inside each other if it's inside a cell.
374
                                        // See https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced#nesting_tables
375
                                        domParentName !== 'td' &&
73✔
376
                                        isTableElement(domParentName)
60✔
377
                                ) {
378
                                        console.error(
60✔
379
                                                'Improper nesting of table. Your <table> should not have a table-node parent.' +
1✔
380
                                                        serializeVNode(vnode) +
1✔
381
                                                        `\n\n${getOwnerStack(vnode)}`
1✔
382
                                        );
383
                                } else if (
384
                                        (type === 'thead' || type === 'tfoot' || type === 'tbody') &&
60✔
385
                                        domParentName !== 'table'
55✔
386
                                ) {
387
                                        console.error(
52✔
388
                                                'Improper nesting of table. Your <thead/tbody/tfoot> should have a <table> parent.' +
52✔
389
                                                        serializeVNode(vnode) +
3✔
390
                                                        `\n\n${getOwnerStack(vnode)}`
3✔
391
                                        );
392
                                } else if (
393
                                        type === 'tr' &&
3✔
394
                                        domParentName !== 'thead' &&
3✔
395
                                        domParentName !== 'tfoot' &&
3✔
396
                                        domParentName !== 'tbody'
3✔
397
                                ) {
398
                                        console.error(
3✔
399
                                                'Improper nesting of table. Your <tr> should have a <thead/tbody/tfoot> parent.' +
3✔
400
                                                        serializeVNode(vnode) +
3✔
401
                                                        `\n\n${getOwnerStack(vnode)}`
3✔
402
                                        );
403
                                } else if (type === 'td' && domParentName !== 'tr') {
3✔
404
                                        console.error(
3✔
405
                                                'Improper nesting of table. Your <td> should have a <tr> parent.' +
3✔
406
                                                        serializeVNode(vnode) +
3✔
407
                                                        `\n\n${getOwnerStack(vnode)}`
3✔
408
                                        );
409
                                } else if (type === 'th' && domParentName !== 'tr') {
3✔
410
                                        console.error(
3✔
411
                                                'Improper nesting of table. Your <th> should have a <tr>.' +
3✔
412
                                                        serializeVNode(vnode) +
3✔
413
                                                        `\n\n${getOwnerStack(vnode)}`
3✔
414
                                        );
415
                                }
416
                        } else if (type === 'p') {
3✔
417
                                let illegalDomChildrenTypes = getDomChildren(vnode).filter(childType =>
107✔
418
                                        ILLEGAL_PARAGRAPH_CHILD_ELEMENTS.test(childType)
8✔
419
                                );
420
                                if (illegalDomChildrenTypes.length) {
4✔
421
                                        console.error(
8✔
422
                                                'Improper nesting of paragraph. Your <p> should not have ' +
8✔
423
                                                        illegalDomChildrenTypes.join(', ') +
3✔
424
                                                        ' as child-elements.' +
3✔
425
                                                        serializeVNode(vnode) +
3✔
426
                                                        `\n\n${getOwnerStack(vnode)}`
3✔
427
                                        );
428
                                }
429
                        } else if (type === 'a' || type === 'button') {
3✔
430
                                if (getDomChildren(vnode).indexOf(type) !== -1) {
39✔
431
                                        console.error(
29✔
432
                                                `Improper nesting of interactive content. Your <${type}>` +
29✔
433
                                                        ` should not have other ${type === 'a' ? 'anchor' : 'button'}` +
4✔
434
                                                        ' tags as child-elements.' +
2✔
435
                                                        serializeVNode(vnode) +
4✔
436
                                                        `\n\n${getOwnerStack(vnode)}`
4✔
437
                                        );
438
                                }
439
                        }
440
                }
4✔
441

442
                hooksAllowed = false;
4✔
443

444
                if (oldDiffed) oldDiffed(vnode);
4✔
445

446
                if (vnode._children != null) {
4✔
447
                        const keys = [];
83✔
448
                        for (let i = 0; i < vnode._children.length; i++) {
83✔
449
                                const child = vnode._children[i];
426✔
450
                                if (!child || child.key == null) continue;
426✔
451

452
                                const key = child.key;
462✔
453
                                if (keys.indexOf(key) !== -1) {
462✔
454
                                        console.error(
17✔
455
                                                'Following component has two or more children with the ' +
17✔
456
                                                        `same key attribute: "${key}". This may cause glitches and misbehavior ` +
5✔
457
                                                        'in rendering process. Component: \n\n' +
458
                                                        serializeVNode(vnode) +
5✔
459
                                                        `\n\n${getOwnerStack(vnode)}`
5✔
460
                                        );
461

462
                                        // Break early to not spam the console
463
                                        break;
5✔
464
                                }
5✔
465

466
                                keys.push(key);
5✔
467
                        }
5✔
468
                }
469

470
                if (vnode._component != null && vnode._component.__hooks != null) {
83✔
471
                        // Validate that none of the hooks in this component contain arguments that are NaN.
472
                        // This is a common mistake that can be hard to debug, so we want to catch it early.
473
                        const hooks = vnode._component.__hooks._list;
83✔
474
                        if (hooks) {
83✔
475
                                for (let i = 0; i < hooks.length; i += 1) {
83✔
476
                                        const hook = hooks[i];
83✔
477
                                        if (hook._args) {
83✔
478
                                                for (let j = 0; j < hook._args.length; j++) {
83✔
479
                                                        const arg = hook._args[j];
83✔
480
                                                        if (isNaN(arg)) {
83✔
481
                                                                const componentName = getDisplayName(vnode);
83✔
482
                                                                console.warn(
83✔
483
                                                                        `Invalid argument passed to hook. Hooks should not be called with NaN in the dependency array. Hook index ${i} in component ${componentName} was called with NaN.`
83✔
484
                                                                );
485
                                                        }
83✔
486
                                                }
487
                                        }
488
                                }
83✔
489
                        }
490
                }
83✔
491
        };
83✔
492
}
83✔
493

494
const setState = Component.prototype.setState;
151!
495
Component.prototype.setState = function (update, callback) {
1✔
496
        if (this._vnode == null) {
1✔
497
                // `this._vnode` will be `null` during componentWillMount. But it
498
                // is perfectly valid to call `setState` during cWM. So we
499
                // need an additional check to verify that we are dealing with a
500
                // call inside constructor.
501
                if (this.state == null) {
1✔
502
                        console.warn(
2✔
503
                                `Calling "this.setState" inside the constructor of a component is a ` +
2✔
504
                                        `no-op and might be a bug in your application. Instead, set ` +
505
                                        `"this.state = {}" directly.\n\n${getOwnerStack(getCurrentVNode())}`
2✔
506
                        );
507
                }
508
        }
509

510
        return setState.call(this, update, callback);
2✔
511
};
2✔
512

513
function isTableElement(type) {
72✔
514
        return (
72✔
515
                type === 'table' ||
72✔
516
                type === 'tfoot' ||
72✔
517
                type === 'tbody' ||
72✔
518
                type === 'thead' ||
72✔
519
                type === 'td' ||
72✔
520
                type === 'tr' ||
72✔
521
                type === 'th'
72✔
522
        );
523
}
72✔
524

525
const ILLEGAL_PARAGRAPH_CHILD_ELEMENTS =
72✔
526
        /^(address|article|aside|blockquote|details|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|main|menu|nav|ol|p|pre|search|section|table|ul)$/;
72✔
527

528
const forceUpdate = Component.prototype.forceUpdate;
1✔
529
Component.prototype.forceUpdate = function (callback) {
1✔
530
        if (this._vnode == null) {
1✔
531
                console.warn(
7✔
532
                        `Calling "this.forceUpdate" inside the constructor of a component is a ` +
7✔
533
                                `no-op and might be a bug in your application.\n\n${getOwnerStack(
1✔
534
                                        getCurrentVNode()
1✔
535
                                )}`
536
                );
537
        } else if (this._parentDom == null) {
1✔
538
                console.warn(
7✔
539
                        `Can't call "this.forceUpdate" on an unmounted component. This is a no-op, ` +
6✔
540
                                `but it indicates a memory leak in your application. To fix, cancel all ` +
541
                                `subscriptions and asynchronous tasks in the componentWillUnmount method.` +
542
                                `\n\n${getOwnerStack(this._vnode)}`
1✔
543
                );
544
        }
545
        return forceUpdate.call(this, callback);
1✔
546
};
1✔
547

548
/**
549
 * Serialize a vnode tree to a string
550
 * @param {import('./internal').VNode} vnode
551
 * @returns {string}
552
 */
553
export function serializeVNode(vnode) {
1✔
554
        let { props } = vnode;
1✔
555
        let name = getDisplayName(vnode);
1✔
556

557
        let attrs = '';
1✔
558
        for (let prop in props) {
1✔
559
                if (props.hasOwnProperty(prop) && prop !== 'children') {
1✔
560
                        let value = props[prop];
1✔
561

562
                        // If it is an object but doesn't have toString(), use Object.toString
563
                        if (typeof value == 'function') {
1✔
564
                                value = `function ${value.displayName || value.name}() {}`;
1!
565
                        }
566

567
                        value =
1✔
568
                                Object(value) === value && !value.toString
1✔
569
                                        ? Object.prototype.toString.call(value)
1!
570
                                        : value + '';
1✔
571

572
                        attrs += ` ${prop}=${JSON.stringify(value)}`;
1✔
573
                }
1✔
574
        }
575

576
        let children = props.children;
1✔
577
        return `<${name}${attrs}${
1✔
578
                children && children.length ? '>..</' + name + '>' : ' />'
1✔
579
        }`;
580
}
1✔
581

582
options._hydrationMismatch = (newVNode, excessDomChildren) => {
7✔
583
        const { type } = newVNode;
1✔
584
        const availableTypes = excessDomChildren
1✔
585
                .map(child => child && child.localName)
1✔
586
                .filter(Boolean);
2✔
587
        console.error(
1✔
588
                `Expected a DOM node of type "${type}" but found "${availableTypes.join(', ')}" as available DOM-node(s), this is caused by the SSR'd HTML containing different DOM-nodes compared to the hydrated one.\n\n${getOwnerStack(newVNode)}`
1✔
589
        );
590
};
1✔
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

© 2026 Coveralls, Inc