-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathpreact10-rst.ts
More file actions
206 lines (183 loc) · 5.76 KB
/
Copy pathpreact10-rst.ts
File metadata and controls
206 lines (183 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/**
* Functions for rendering components using Preact v10 and converting the result
* to a React Standard Tree (RST) format defined by Enzyme.
*
* Preact 10 stores details of the rendered elements on internal fields of
* the VNodes. A reference to the vnode is stored in the root DOM element.
* The rendered result is converted to RST by traversing these vnode references.
*/
import type { NodeType, RSTNodeChild, RSTNode } from 'enzyme';
import type { Component, ComponentChild, VNode } from 'preact';
import { isValidElement, Fragment } from 'preact';
import { childElements } from './compat.js';
import {
getChildren,
getComponent,
getDOMNode,
getLastRenderOutput,
getLastVNodeRenderedIntoContainer,
} from './preact10-internals.js';
import { getRealType } from './shallow-render-utils.js';
type Props = { [prop: string]: any };
function stripSpecialProps(props: Props, preserveChildrenProp = false) {
if (preserveChildrenProp) {
const { key, ref, ...otherProps } = props;
return otherProps;
} else {
const { children, key, ref, ...otherProps } = props;
return otherProps;
}
}
function convertDOMProps(props: Props, preserveChildrenProp = false) {
const srcProps = stripSpecialProps(props, preserveChildrenProp);
const converted: Props = {};
Object.keys(srcProps).forEach(srcProp => {
const destProp = srcProp === 'class' ? 'className' : srcProp;
converted[destProp] = props[srcProp];
});
return converted;
}
/**
* Convert the rendered output of a vnode to RST nodes.
*/
function rstNodesFromChildren(nodes: (VNode | null)[] | null): RSTNodeChild[] {
if (!nodes) {
return [];
}
return nodes.flatMap(node => {
if (node === null) {
// The array of rendered children may have `null` entries as a result of
// eg. conditionally rendered children where the condition was false.
//
// These are omitted from the rendered tree that Enzyme works with.
return [];
}
const rst = rstNodeFromVNode(node);
return Array.isArray(rst) ? rst : [rst];
});
}
function rstNodeFromVNode(node: VNode | null): RSTNodeChild | RSTNodeChild[] {
if (node == null) {
return null;
}
// Preact 10 represents text nodes as VNodes with `node.type == null` and
// `node.props` equal to the string content.
if (
typeof node.props === 'string' ||
typeof node.props === 'number' ||
typeof node.props === 'bigint'
) {
return String(node.props);
}
if (node.type === Fragment) {
return rstNodesFromChildren(getChildren(node));
}
const component = getComponent(node);
if (component) {
return rstNodeFromComponent(node, component);
}
if (!getDOMNode(node)) {
throw new Error(
`Expected VDOM node to be a DOM node but got ${node.type!}`
);
}
return {
nodeType: 'host',
type: node.type!,
props: convertDOMProps(node.props!),
key: node.key || null,
ref: node.ref || null,
instance: getDOMNode(node),
rendered: rstNodesFromChildren(getChildren(node)),
};
}
export function nodeTypeFromType(type: any): NodeType {
if (typeof type === 'string') {
return 'host';
} else if (type.prototype && typeof type.prototype.render === 'function') {
return 'class';
} else if (typeof type === 'function') {
return 'function';
} else {
throw new Error(`Unknown node type: ${type}`);
}
}
/**
* Convert a JSX element tree returned by Preact's `h` function into an RST
* node.
*/
export function rstNodeFromElement(
node: ComponentChild,
preserveChildrenProp = false
): RSTNodeChild {
if (!isValidElement(node)) {
return node;
}
const children = childElements(node)
.flat()
.map(child => rstNodeFromElement(child, preserveChildrenProp));
const nodeType = nodeTypeFromType(node.type);
let props = {};
if (typeof node.props === 'object' && node.props) {
props =
nodeType === 'host'
? convertDOMProps(node.props, preserveChildrenProp)
: stripSpecialProps(node.props, preserveChildrenProp);
}
const ref = node.ref || null;
return {
nodeType,
type: node.type as NodeType,
props,
key: node.key || null,
ref,
instance: getComponent(node),
rendered: children,
};
}
/**
* Return a React Standard Tree (RST) node from a Preact `Component` instance.
*/
function rstNodeFromComponent(vnode: VNode, component: Component): RSTNode {
const nodeType = nodeTypeFromType(component.constructor);
const rendered = rstNodesFromChildren(getLastRenderOutput(component));
// If this was a shallow-rendered component, set the RST node's type to the
// real component function/class.
const shallowRenderedType = getRealType(component);
const type = shallowRenderedType
? shallowRenderedType
: component.constructor;
return {
nodeType,
type,
props: { children: [], ...component.props },
key: vnode.key || null,
ref: vnode.ref || null,
instance: component,
rendered,
};
}
/**
* Convert the Preact components rendered into `container` into an RST node.
*/
export function getNode(container: HTMLElement): RSTNode | null {
const rendered = getLastVNodeRenderedIntoContainer(container);
const rstNode = rstNodeFromVNode(rendered);
// There is currently a requirement that the root element produces a single
// RST node. Fragments do not appear in the RST tree, so it is fine if the
// root node is a fragment, provided that it renders only a single child. In
// fact Preact itself wraps the root element in a single-child fragment.
if (Array.isArray(rstNode)) {
if (rstNode.length === 1) {
return rstNode[0] as RSTNode;
} else if (rstNode.length === 0) {
return null;
} else {
throw new Error(
'Root element must not be a fragment with multiple children'
);
}
} else {
return rstNode as RSTNode;
}
}