Skip to content

Commit 5f66965

Browse files
committed
greenkeep CI
1 parent 7ccf4d3 commit 5f66965

File tree

7 files changed

+1932
-359
lines changed

7 files changed

+1932
-359
lines changed

_tests/ReactSixteenAdapter.js

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
/**
2+
* enzyme-adapter-react-16
3+
* Temporary solution for React Fiber incompatibility issue with regard to the Context API
4+
* @link https://github.com/diegohaz/constate/blob/93b7b5b469be4521784b51380f49e6589c3e56b9/test/ReactSixteenAdapter.js
5+
* @Link https://github.com/airbnb/enzyme/issues/1509
6+
*/
7+
/* eslint-disable */
8+
import React from 'react'
9+
import ReactDOM from 'react-dom'
10+
import ReactDOMServer from 'react-dom/server'
11+
import ShallowRenderer from 'react-test-renderer/shallow'
12+
import TestUtils from 'react-dom/test-utils'
13+
import { EnzymeAdapter } from 'enzyme'
14+
import {
15+
elementToTree,
16+
nodeTypeFromType,
17+
mapNativeEventNames,
18+
propFromEvent,
19+
assertDomAvailable,
20+
withSetStateAllowed,
21+
createRenderWrapper,
22+
createMountWrapper,
23+
propsWithKeysAndRef,
24+
} from 'enzyme-adapter-utils'
25+
import { findCurrentFiberUsingSlowPath } from 'react-reconciler/reflection'
26+
27+
function ensureKeyOrUndefined(key) {
28+
return key || (key === '' ? '' : undefined);
29+
}
30+
31+
const HostRoot = 3
32+
const ClassComponent = 2
33+
const Fragment = 10
34+
const FunctionalComponent = 1
35+
const HostPortal = 4
36+
const HostComponent = 5
37+
const HostText = 6
38+
const Mode = 11
39+
const ContextConsumer = 12
40+
const ContextProvider = 13
41+
42+
function nodeAndSiblingsArray(nodeWithSibling) {
43+
const array = []
44+
let node = nodeWithSibling
45+
while (node != null) {
46+
array.push(node)
47+
node = node.sibling
48+
}
49+
return array
50+
}
51+
52+
function flatten(arr) {
53+
const result = []
54+
const stack = [{ i: 0, array: arr }]
55+
while (stack.length) {
56+
const n = stack.pop()
57+
while (n.i < n.array.length) {
58+
const el = n.array[n.i]
59+
n.i += 1
60+
if (Array.isArray(el)) {
61+
stack.push(n)
62+
stack.push({ i: 0, array: el })
63+
break
64+
}
65+
result.push(el)
66+
}
67+
}
68+
return result
69+
}
70+
71+
function toTree(vnode) {
72+
if (vnode == null) {
73+
return null
74+
}
75+
// TODO(lmr): I'm not really sure I understand whether or not this is what
76+
// i should be doing, or if this is a hack for something i'm doing wrong
77+
// somewhere else. Should talk to sebastian about this perhaps
78+
const node = findCurrentFiberUsingSlowPath(vnode)
79+
switch (node.tag) {
80+
case HostRoot: // 3
81+
return toTree(node.child)
82+
case HostPortal: // 4
83+
return toTree(node.child)
84+
case ClassComponent:
85+
return {
86+
nodeType: 'class',
87+
type: node.type,
88+
props: { ...node.memoizedProps },
89+
key: ensureKeyOrUndefined(node.key),
90+
ref: node.ref,
91+
instance: node.stateNode,
92+
rendered: childrenToTree(node.child),
93+
}
94+
case FunctionalComponent: // 1
95+
return {
96+
nodeType: 'function',
97+
type: node.type,
98+
props: { ...node.memoizedProps },
99+
key: ensureKeyOrUndefined(node.key),
100+
ref: node.ref,
101+
instance: null,
102+
rendered: childrenToTree(node.child),
103+
}
104+
case HostComponent: {
105+
// 5
106+
let renderedNodes = flatten(nodeAndSiblingsArray(node.child).map(toTree))
107+
if (renderedNodes.length === 0) {
108+
renderedNodes = [node.memoizedProps.children]
109+
}
110+
return {
111+
nodeType: 'host',
112+
type: node.type,
113+
props: { ...node.memoizedProps },
114+
key: ensureKeyOrUndefined(node.key),
115+
ref: node.ref,
116+
instance: node.stateNode,
117+
rendered: renderedNodes,
118+
}
119+
}
120+
case HostText: // 6
121+
return node.memoizedProps
122+
case Fragment: // 10
123+
case Mode: // 11
124+
case ContextProvider: // 13
125+
case ContextConsumer: // 12
126+
return childrenToTree(node.child)
127+
default:
128+
throw new Error(
129+
`Enzyme Internal Error: unknown node with tag ${node.tag}`,
130+
)
131+
}
132+
}
133+
134+
function childrenToTree(node) {
135+
if (!node) {
136+
return null
137+
}
138+
const children = nodeAndSiblingsArray(node)
139+
if (children.length === 0) {
140+
return null
141+
} else if (children.length === 1) {
142+
return toTree(children[0])
143+
}
144+
return flatten(children.map(toTree))
145+
}
146+
147+
function nodeToHostNode(_node) {
148+
// NOTE(lmr): node could be a function component
149+
// which wont have an instance prop, but we can get the
150+
// host node associated with its return value at that point.
151+
// Although this breaks down if the return value is an array,
152+
// as is possible with React 16.
153+
let node = _node
154+
while (node && !Array.isArray(node) && node.instance === null) {
155+
node = node.rendered
156+
}
157+
if (Array.isArray(node)) {
158+
// TODO(lmr): throw warning regarding not being able to get a host node here
159+
throw new Error('Trying to get host node of an array')
160+
}
161+
// if the SFC returned null effectively, there is no host node.
162+
if (!node) {
163+
return null
164+
}
165+
return ReactDOM.findDOMNode(node.instance)
166+
}
167+
168+
class ReactSixteenAdapter extends EnzymeAdapter {
169+
constructor() {
170+
super()
171+
this.options = {
172+
...this.options,
173+
enableComponentDidUpdateOnSetState: true,
174+
}
175+
}
176+
createMountRenderer(options) {
177+
assertDomAvailable('mount')
178+
const domNode = options.attachTo || global.document.createElement('div')
179+
let instance = null
180+
return {
181+
render(el, context, callback) {
182+
if (instance === null) {
183+
const ReactWrapperComponent = createMountWrapper(el, options)
184+
const wrappedEl = React.createElement(ReactWrapperComponent, {
185+
Component: el.type,
186+
props: el.props,
187+
context,
188+
})
189+
instance = ReactDOM.render(wrappedEl, domNode)
190+
if (typeof callback === 'function') {
191+
callback()
192+
}
193+
} else {
194+
instance.setChildProps(el.props, context, callback)
195+
}
196+
},
197+
unmount() {
198+
ReactDOM.unmountComponentAtNode(domNode)
199+
instance = null
200+
},
201+
getNode() {
202+
return instance ? toTree(instance._reactInternalFiber).rendered : null
203+
},
204+
simulateEvent(node, event, mock) {
205+
const mappedEvent = mapNativeEventNames(event)
206+
const eventFn = TestUtils.Simulate[mappedEvent]
207+
if (!eventFn) {
208+
throw new TypeError(
209+
`ReactWrapper::simulate() event '${event}' does not exist`,
210+
)
211+
}
212+
// eslint-disable-next-line react/no-find-dom-node
213+
eventFn(nodeToHostNode(node), mock)
214+
},
215+
batchedUpdates(fn) {
216+
return fn()
217+
// return ReactDOM.unstable_batchedUpdates(fn);
218+
},
219+
}
220+
}
221+
222+
createShallowRenderer(/* options */) {
223+
const renderer = new ShallowRenderer()
224+
let isDOM = false
225+
let cachedNode = null
226+
return {
227+
render(el, context) {
228+
cachedNode = el
229+
/* eslint consistent-return: 0 */
230+
if (typeof el.type === 'string') {
231+
isDOM = true
232+
} else {
233+
isDOM = false
234+
return withSetStateAllowed(() => renderer.render(el, context))
235+
}
236+
},
237+
unmount() {
238+
renderer.unmount()
239+
},
240+
getNode() {
241+
if (isDOM) {
242+
return elementToTree(cachedNode)
243+
}
244+
const output = renderer.getRenderOutput()
245+
return {
246+
nodeType: nodeTypeFromType(cachedNode.type),
247+
type: cachedNode.type,
248+
props: cachedNode.props,
249+
key: ensureKeyOrUndefined(cachedNode.key),
250+
ref: cachedNode.ref,
251+
instance: renderer._instance,
252+
rendered: Array.isArray(output)
253+
? flatten(output).map(elementToTree)
254+
: elementToTree(output),
255+
}
256+
},
257+
simulateEvent(node, event, ...args) {
258+
const handler = node.props[propFromEvent(event)]
259+
if (handler) {
260+
withSetStateAllowed(() => {
261+
// TODO(lmr): create/use synthetic events
262+
// TODO(lmr): emulate React's event propagation
263+
// ReactDOM.unstable_batchedUpdates(() => {
264+
handler(...args)
265+
// });
266+
})
267+
}
268+
},
269+
batchedUpdates(fn) {
270+
return fn()
271+
// return ReactDOM.unstable_batchedUpdates(fn);
272+
},
273+
}
274+
}
275+
276+
createStringRenderer(options) {
277+
return {
278+
render(el, context) {
279+
if (
280+
options.context &&
281+
(el.type.contextTypes || options.childContextTypes)
282+
) {
283+
const childContextTypes = {
284+
...(el.type.contextTypes || {}),
285+
...options.childContextTypes,
286+
}
287+
const ContextWrapper = createRenderWrapper(
288+
el,
289+
context,
290+
childContextTypes,
291+
)
292+
return ReactDOMServer.renderToStaticMarkup(
293+
React.createElement(ContextWrapper),
294+
)
295+
}
296+
return ReactDOMServer.renderToStaticMarkup(el)
297+
},
298+
}
299+
}
300+
301+
// Provided a bag of options, return an `EnzymeRenderer`. Some options can be implementation
302+
// specific, like `attach` etc. for React, but not part of this interface explicitly.
303+
// eslint-disable-next-line class-methods-use-this, no-unused-vars
304+
createRenderer(options) {
305+
switch (options.mode) {
306+
case EnzymeAdapter.MODES.MOUNT:
307+
return this.createMountRenderer(options)
308+
case EnzymeAdapter.MODES.SHALLOW:
309+
return this.createShallowRenderer(options)
310+
case EnzymeAdapter.MODES.STRING:
311+
return this.createStringRenderer(options)
312+
default:
313+
throw new Error(
314+
`Enzyme Internal Error: Unrecognized mode: ${options.mode}`,
315+
)
316+
}
317+
}
318+
319+
// converts an RSTNode to the corresponding JSX Pragma Element. This will be needed
320+
// in order to implement the `Wrapper.mount()` and `Wrapper.shallow()` methods, but should
321+
// be pretty straightforward for people to implement.
322+
// eslint-disable-next-line class-methods-use-this, no-unused-vars
323+
nodeToElement(node) {
324+
if (!node || typeof node !== 'object') return null
325+
return React.createElement(node.type, propsWithKeysAndRef(node))
326+
}
327+
328+
elementToNode(element) {
329+
return elementToTree(element)
330+
}
331+
332+
nodeToHostNode(node) {
333+
return nodeToHostNode(node)
334+
}
335+
336+
isValidElement(element) {
337+
return React.isValidElement(element)
338+
}
339+
340+
createElement(...args) {
341+
return React.createElement(...args)
342+
}
343+
}
344+
345+
module.exports = ReactSixteenAdapter
346+
347+
// PS: Fuck you enzyme! https://github.com/airbnb/enzyme/issues/1509

_tests/memoize.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import createContext from 'create-react-context';
33
import Enzyme, {mount} from 'enzyme';
4-
import Adapter from 'enzyme-adapter-react-16';
4+
import Adapter from './ReactSixteenAdapter';
55
import Memoize, {MemoizeContext, MemoizedFlow, MemoizedRender} from '../src';
66

77
Enzyme.configure({adapter: new Adapter()});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"jest": "^23.0.0",
6363
"package-self": "^1.1.1",
6464
"react": "16.4.0",
65-
"react-dom": "^16.2.0",
65+
"react-dom": "^16.4.0",
6666
"react-test-renderer": "^16.4.0",
6767
"size-limit": "^0.18.1"
6868
},

src/Context.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@ import PropTypes from 'prop-types';
33
import MemoizedState from './Memoizer';
44

55

6-
export const MemoizeContext = ({ consumer: Consumer, selector, pure, children, ...rest }) => (
6+
export const MemoizeContext = ({
7+
consumer: Consumer,
8+
selector,
9+
pure,
10+
children,
11+
...rest
12+
}) => (
713
<Consumer {...rest}>
814
{values =>
9-
(<MemoizedState {...values} compute={selector} pure={pure}>
10-
{result => children(result)}
11-
</MemoizedState>)
15+
(
16+
<MemoizedState {...values} compute={selector} pure={pure}>
17+
{result => children(result)}
18+
</MemoizedState>
19+
)
1220
}
1321
</Consumer>
1422
);

0 commit comments

Comments
 (0)