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

preactjs / preact / 13604982284

01 Mar 2025 12:54PM UTC coverage: 99.454% (-0.2%) from 99.61%
13604982284

Pull #4549

github

web-flow
Merge 8b258f079 into ccd1e71e5
Pull Request #4549: (major) - Tracking PR for v11

1087 of 1113 branches covered (97.66%)

25 of 28 new or added lines in 11 files covered. (89.29%)

1 existing line in 1 file now uncovered.

729 of 733 relevant lines covered (99.45%)

12212.63 hits per line

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

94.12
/compat/src/render.js
1
import {
2
        render as preactRender,
3
        hydrate as preactHydrate,
4
        options,
5
        toChildArray,
6
        Component
7
} from 'preact';
8
import {
9
        useCallback,
10
        useContext,
11
        useDebugValue,
12
        useEffect,
13
        useId,
14
        useImperativeHandle,
15
        useLayoutEffect,
16
        useMemo,
17
        useReducer,
18
        useRef,
19
        useState
20
} from 'preact/hooks';
21
import {
22
        useDeferredValue,
23
        useInsertionEffect,
24
        useSyncExternalStore,
25
        useTransition
26
} from './index';
27
import { assign, IS_NON_DIMENSIONAL } from './util';
28

29
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
30✔
30

31
const CAMEL_PROPS =
32
        /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
30✔
33
const ON_ANI = /^on(Ani|Tra|Tou|BeforeInp|Compo)/;
30✔
34
const CAMEL_REPLACE = /[A-Z0-9]/g;
30✔
35
const IS_DOM = typeof document !== 'undefined';
30✔
36

37
// Input types for which onchange should not be converted to oninput.
38
const onChangeInputType = type => /fil|che|rad/.test(type);
30✔
39

40
// Some libraries like `react-virtualized` explicitly check for this.
41
Component.prototype.isReactComponent = {};
30✔
42

43
/**
44
 * Proxy render() since React returns a Component reference.
45
 * @param {import('./internal').VNode} vnode VNode tree to render
46
 * @param {import('./internal').PreactElement} parent DOM node to render vnode tree into
47
 * @param {() => void} [callback] Optional callback that will be called after rendering
48
 * @returns {import('./internal').Component | null} The root component reference or null
49
 */
50
export function render(vnode, parent, callback) {
51
        // React destroys any existing DOM nodes, see #1727
52
        // ...but only on the first render, see #1828
53
        if (parent._children == null) {
421✔
54
                parent.textContent = '';
55
        }
56

57
        preactRender(vnode, parent);
58
        if (typeof callback == 'function') callback();
224✔
59

60
        return vnode ? vnode._component : null;
227✔
61
}
62

63
export function hydrate(vnode, parent, callback) {
64
        preactHydrate(vnode, parent);
65
        if (typeof callback == 'function') callback();
26✔
66

67
        return vnode ? vnode._component : null;
25!
68
}
69

70
let oldEventHook = options.event;
30✔
71
options.event = e => {
30✔
72
        if (oldEventHook) e = oldEventHook(e);
43✔
73

74
        e.persist = empty;
75
        e.isPropagationStopped = isPropagationStopped;
76
        e.isDefaultPrevented = isDefaultPrevented;
77
        return (e.nativeEvent = e);
42✔
78
};
79

80
function empty() {}
81

82
function isPropagationStopped() {
83
        return this.cancelBubble;
3✔
84
}
85

86
function isDefaultPrevented() {
87
        return this.defaultPrevented;
3✔
88
}
89

90
const classNameDescriptorNonEnumberable = {
30✔
91
        enumerable: false,
92
        configurable: true,
93
        get() {
94
                return this.class;
2✔
95
        }
96
};
97

98
function handleDomVNode(vnode) {
99
        let props = vnode.props,
681✔
100
                type = vnode.type,
681✔
101
                normalizedProps = {};
681✔
102

103
        let isNonDashedType = type.indexOf('-') === -1;
681✔
104
        for (let i in props) {
681✔
105
                let value = props[i];
822✔
106

107
                if (
822✔
108
                        (i === 'value' && 'defaultValue' in props && value == null) ||
4,683✔
109
                        // Emulate React's behavior of not rendering the contents of noscript tags on the client.
110
                        (IS_DOM && i === 'children' && type === 'noscript') ||
111
                        i === 'class' ||
112
                        i === 'className'
113
                ) {
114
                        // Skip applying value if it is null/undefined and we already set
115
                        // a default value
116
                        continue;
117
                }
118

119
                let lowerCased = i.toLowerCase();
789✔
120
                if (i === 'style' && typeof value === 'object') {
789✔
121
                        for (let key in value) {
2✔
122
                                if (typeof value[key] === 'number' && !IS_NON_DIMENSIONAL.test(key)) {
145✔
123
                                        value[key] += 'px';
124
                                }
125
                        }
126
                } else if (
127
                        i === 'defaultValue' &&
787✔
128
                        'value' in props &&
129
                        props.value == null
130
                ) {
131
                        // `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
132
                        // `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
133
                        i = 'value';
134
                } else if (i === 'download' && value === true) {
1,572✔
135
                        // Calling `setAttribute` with a truthy value will lead to it being
136
                        // passed as a stringified value, e.g. `download="true"`. React
137
                        // converts it to an empty string instead, otherwise the attribute
138
                        // value will be used as the file name and the file will be called
139
                        // "true" upon downloading it.
140
                        value = '';
141
                } else if (lowerCased === 'translate' && value === 'no') {
1,570✔
142
                        value = false;
143
                } else if (lowerCased[0] === 'o' && lowerCased[1] === 'n') {
1,651✔
144
                        if (lowerCased === 'ondoubleclick') {
83✔
145
                                i = 'ondblclick';
146
                        } else if (
147
                                lowerCased === 'onchange' &&
198✔
148
                                (type === 'input' || type === 'textarea') &&
149
                                !onChangeInputType(props.type)
150
                        ) {
151
                                lowerCased = i = 'oninput';
152
                        } else if (lowerCased === 'onfocus') {
71✔
153
                                i = 'onfocusin';
154
                        } else if (lowerCased === 'onblur') {
70✔
155
                                i = 'onfocusout';
156
                        } else if (ON_ANI.test(i)) {
78✔
157
                                i = lowerCased;
158
                        }
159
                } else if (isNonDashedType && CAMEL_PROPS.test(i)) {
2,100✔
160
                        i = i.replace(CAMEL_REPLACE, '-$&').toLowerCase();
161
                } else if (value === null) {
684✔
162
                        value = undefined;
163
                }
164

165
                // Add support for onInput and onChange, see #3561
166
                // if we have an oninput prop already change it to oninputCapture
167
                if (lowerCased === 'oninput') {
789✔
168
                        i = lowerCased;
169
                        if (normalizedProps[i]) {
170
                                i = 'oninputCapture';
171
                        }
172
                }
173

174
                normalizedProps[i] = value;
175
        }
176

177
        // Add support for array select values: <select multiple value={[]} />
178
        if (
179
                type == 'select' &&
681✔
180
                normalizedProps.multiple &&
181
                Array.isArray(normalizedProps.value)
182
        ) {
183
                // forEach() always returns undefined, which we abuse here to unset the value prop.
184
                normalizedProps.value = toChildArray(props.children).forEach(child => {
185
                        child.props.selected =
3✔
186
                                normalizedProps.value.indexOf(child.props.value) != -1;
187
                });
188
        }
189

190
        // Adding support for defaultValue in select tag
191
        if (type == 'select' && normalizedProps.defaultValue != null) {
686✔
192
                normalizedProps.value = toChildArray(props.children).forEach(child => {
193
                        if (normalizedProps.multiple) {
6✔
194
                                child.props.selected =
6✔
195
                                        normalizedProps.defaultValue.indexOf(child.props.value) != -1;
196
                        } else {
197
                                child.props.selected =
198
                                        normalizedProps.defaultValue == child.props.value;
199
                        }
200
                });
201
        }
202

203
        if (props.class && !props.className) {
1,375✔
204
                normalizedProps.class = props.class;
205
                Object.defineProperty(
206
                        normalizedProps,
207
                        'className',
208
                        classNameDescriptorNonEnumberable
1,367✔
209
                );
210
        } else if (props.className && !props.class) {
211
                normalizedProps.class = normalizedProps.className = props.className;
212
        } else if (props.class && props.className) {
213
                normalizedProps.class = normalizedProps.className = props.className;
214
        }
215

216
        vnode.props = normalizedProps;
217
}
218

219
let oldVNodeHook = options.vnode;
30✔
220
options.vnode = vnode => {
30✔
221
        // only normalize props on Element nodes
222
        if (typeof vnode.type === 'string') {
3,113✔
223
                handleDomVNode(vnode);
224
        } else if (typeof vnode.type === 'function' && vnode.type.defaultProps) {
2,432!
NEW
225
                let normalizedProps = assign({}, vnode.props);
×
NEW
226
                for (let i in vnode.type.defaultProps) {
×
227
                        if (normalizedProps[i] === undefined) {
228
                                normalizedProps[i] = vnode.type.defaultProps[i];
229
                        }
230
                }
NEW
231
                vnode.props = normalizedProps;
×
232
        }
233
        vnode.$$typeof = REACT_ELEMENT_TYPE;
3,113✔
234

235
        if (oldVNodeHook) oldVNodeHook(vnode);
3,140✔
236
};
237

238
// Only needed for react-relay
239
let currentComponent;
240
const oldBeforeRender = options._render;
30✔
241
options._render = function (vnode) {
30✔
242
        if (oldBeforeRender) {
1,834✔
243
                oldBeforeRender(vnode);
244
        }
245
        currentComponent = vnode._component;
246
};
247

248
const oldDiffed = options.diffed;
30✔
249
/** @type {(vnode: import('./internal').VNode) => void} */
250
options.diffed = function (vnode) {
30✔
251
        if (oldDiffed) {
3,063✔
252
                oldDiffed(vnode);
253
        }
254

255
        const props = vnode.props;
3,063✔
256
        const dom = vnode._dom;
3,063✔
257

258
        if (
259
                dom != null &&
3,063✔
260
                vnode.type === 'textarea' &&
261
                'value' in props &&
262
                props.value !== dom.value
263
        ) {
264
                dom.value = props.value == null ? '' : props.value;
1!
265
        }
266

267
        currentComponent = null;
268
};
269

270
// This is a very very private internal function for React it
271
// is used to sort-of do runtime dependency injection.
272
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
30✔
273
        ReactCurrentDispatcher: {
274
                current: {
275
                        readContext(context) {
276
                                return currentComponent._globalContext[context._id].props.value;
1✔
277
                        },
278
                        useCallback,
279
                        useContext,
280
                        useDebugValue,
281
                        useDeferredValue,
282
                        useEffect,
283
                        useId,
284
                        useImperativeHandle,
285
                        useInsertionEffect,
286
                        useLayoutEffect,
287
                        useMemo,
288
                        // useMutableSource, // experimental-only and replaced by uSES, likely not worth supporting
289
                        useReducer,
290
                        useRef,
291
                        useState,
292
                        useSyncExternalStore,
293
                        useTransition
294
                }
295
        }
296
};
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