react-component-snapshot 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,79 @@
1
1
  # react-component-snapshot
2
2
 
3
- Transform a React component to match its snapshot in Jest
3
+ Transform a React component to match its snapshot in **Jest** with **Testing Library**
4
4
 
5
5
  ```javascript
6
6
  import snapshotOf from 'react-component-snapshot'
7
+ ```
8
+
9
+ ## With `@testing-library/react`
10
+
11
+ Get a snapshot of the component element from the `render` result
12
+
13
+ ```javascript
14
+ import {
15
+ render
16
+ } from '@testing-library/react'
17
+
18
+ import {
19
+ toSnapshot
20
+ } from 'react-component-snapshot'
21
+
22
+ describe('`toSnapshot`', () => {
23
+ it('matches the snapshot', () => {
24
+ function Component () {
25
+ return (
26
+ <div className='grand-parent'>
27
+ <div className='parent'>
28
+ <div className='child'>
29
+ TEXT
30
+ </div>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
7
35
 
8
- const {
9
- container: {
10
- firstElementChild: element
36
+ expect(toSnapshot(render(
37
+ <Component />
38
+ )))
39
+ .toMatchSnapshot()
40
+ })
41
+ })
42
+ ```
43
+
44
+ Get a snapshot of any component element
45
+
46
+ ```javascript
47
+ import {
48
+ render
49
+ } from '@testing-library/react'
50
+
51
+ import {
52
+ snapshotOf
53
+ } from 'react-component-snapshot'
54
+
55
+ describe('`snapshotOf`', () => {
56
+ function Component () {
57
+ return (
58
+ <div className='grand-parent'>
59
+ <div className='parent'>
60
+ <div className='child'>
61
+ TEXT
62
+ </div>
63
+ </div>
64
+ </div>
65
+ )
11
66
  }
12
- } = render(
13
- <Component />
14
- )
15
67
 
16
- expect(snapshotOf(element))
17
- .toMatchSnapshot()
68
+ it('matches the snapshot', () => {
69
+ const {
70
+ container
71
+ } = render(
72
+ <Component />
73
+ )
74
+
75
+ expect(snapshotOf(container.querySelector('.child')))
76
+ .toMatchSnapshot()
77
+ })
78
+ })
18
79
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-component-snapshot",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Transform a React component to match its snapshot in Jest",
5
5
  "keywords": [
6
6
  "react",
@@ -0,0 +1,239 @@
1
+ /**
2
+ * @typedef {import('@testing-library/react').RenderResult} RenderType
3
+ * @typedef {ReactComponentSnapshotTypes.FiberNode} FiberNode
4
+ */
5
+
6
+ import {
7
+ getComponentNameFromFiber as getType
8
+ } from 'react-component-name'
9
+
10
+ import {
11
+ REACT_COMPONENT_SNAPSHOT
12
+ } from './symbols.mjs'
13
+
14
+ /**
15
+ * @param {unknown} v
16
+ * @returns {v is object}
17
+ */
18
+ function isObject (v) {
19
+ return (v || false) instanceof Object && !Array.isArray(v)
20
+ }
21
+
22
+ /**
23
+ * @param {PropertyKey} key
24
+ * @returns {boolean}
25
+ */
26
+ function isReactFiberKey (key) {
27
+ return String(key).startsWith('__reactFiber$')
28
+ }
29
+
30
+ /**
31
+ * @param {PropertyKey} key
32
+ * @returns {boolean}
33
+ */
34
+ function isReactPropsKey (key) {
35
+ return String(key).startsWith('__reactProps$')
36
+ }
37
+
38
+ /**
39
+ * @param {Element | Text | React.Component<any, any, any>} stateNode
40
+ * @returns {string | undefined}
41
+ */
42
+ function getReactFiberKey (stateNode) {
43
+ return (
44
+ Object.keys(stateNode)
45
+ .find(isReactFiberKey)
46
+ )
47
+ }
48
+
49
+ /**
50
+ * @param {Element | Text | React.Component<any, any, any>} stateNode
51
+ * @returns {string | undefined}
52
+ */
53
+ function getReactPropsKey (stateNode) {
54
+ return (
55
+ Object.keys(stateNode)
56
+ .find(isReactPropsKey)
57
+ )
58
+ }
59
+
60
+ /**
61
+ * @param {Element | Text | React.Component<any, any, any>} stateNode
62
+ * @returns {Record<PropertyKey, unknown> | null}
63
+ */
64
+ export function getStateNodeFiber (stateNode) {
65
+ const key = getReactFiberKey(stateNode)
66
+
67
+ if (key) {
68
+ const { // @ts-expect-error
69
+ [key]: fiber = null
70
+ } = stateNode
71
+
72
+ return fiber
73
+ }
74
+
75
+ return null
76
+ }
77
+
78
+ /**
79
+ * @param {React.Component<any, any, any> | Element | Text} stateNode
80
+ * @returns {Record<PropertyKey, unknown> | null}
81
+ */
82
+ export function getStateNodeProps (stateNode) {
83
+ const key = getReactPropsKey(stateNode)
84
+
85
+ if (key) {
86
+ const { // @ts-expect-error
87
+ [key]: props = null
88
+ } = stateNode
89
+
90
+ return props
91
+ }
92
+
93
+ return null
94
+ }
95
+
96
+ /**
97
+ * @param {Element} element
98
+ * @returns {FiberNode | null}
99
+ */
100
+ export function getFiber (element) {
101
+ const key = getReactFiberKey(element)
102
+
103
+ if (key) {
104
+ const { // @ts-expect-error
105
+ [key]: fiber = null
106
+ } = element
107
+
108
+ return fiber
109
+ }
110
+
111
+ return null
112
+ }
113
+
114
+ /**
115
+ * @param {FiberNode} fiberNode
116
+ * @returns {FiberNode[]}
117
+ */
118
+ function getSiblings (fiberNode) {
119
+ const siblings = []
120
+
121
+ let sibling = fiberNode.sibling
122
+ while (sibling) {
123
+ siblings.push(sibling)
124
+ sibling = sibling.sibling
125
+ }
126
+
127
+ return siblings
128
+ }
129
+
130
+ /**
131
+ * @param {FiberNode} fiberNode
132
+ * @returns {Record<PropertyKey, unknown>}
133
+ */
134
+ function getProps (fiberNode) {
135
+ const stateNode = fiberNode.stateNode
136
+
137
+ /**
138
+ * State node may be null on class components
139
+ */
140
+ if (stateNode) {
141
+ const props = getStateNodeProps(stateNode) ?? {}
142
+
143
+ return (
144
+ Object.fromEntries(
145
+ Object.entries(props)
146
+ .filter(([key]) => key !== 'children')
147
+ .filter(([key, value]) => {
148
+ if (isObject(value)) return !('current' in value)
149
+ return true
150
+ })
151
+ )
152
+ )
153
+ }
154
+
155
+ return {}
156
+ }
157
+
158
+ /**
159
+ * @param {FiberNode} fiberNode
160
+ * @returns {Array<string | FiberNode>}
161
+ */
162
+ function getChildrenOf (fiberNode) {
163
+ const child = fiberNode.child
164
+
165
+ if (child) {
166
+ const siblings = getSiblings(child)
167
+
168
+ return [child].concat(siblings).map((child) => {
169
+ const stateNode = child.stateNode
170
+
171
+ if (stateNode instanceof Text) {
172
+ return stateNode.data
173
+ }
174
+
175
+ return child
176
+ })
177
+ }
178
+
179
+ const stateNode = fiberNode.stateNode
180
+
181
+ if (stateNode) {
182
+ /**
183
+ * Get the text as a prop from the instance
184
+ */
185
+ const {
186
+ children
187
+ } = getStateNodeProps(stateNode) ?? {}
188
+
189
+ if (typeof children === 'string') {
190
+ return [children]
191
+ }
192
+ }
193
+
194
+ return []
195
+ }
196
+
197
+ /**
198
+ * @param {FiberNode | string} child
199
+ * @returns {string | Record<PropertyKey, unknown>}
200
+ */
201
+ function toChildren (child) {
202
+ if (typeof child === 'string') return child
203
+
204
+ return (
205
+ getSnapshot(child)
206
+ )
207
+ }
208
+
209
+ /**
210
+ * @param {FiberNode} fiberNode
211
+ * @returns {Array<string | Record<PropertyKey, unknown>>}
212
+ */
213
+ function getChildren (fiberNode) {
214
+ return (
215
+ getChildrenOf(fiberNode)
216
+ .map(toChildren)
217
+ )
218
+ }
219
+
220
+ /**
221
+ * @param {FiberNode} fiberNode
222
+ * @returns {Record<PropertyKey, unknown>}
223
+ */
224
+ export default function getSnapshot (fiberNode) {
225
+ const type = getType(fiberNode)
226
+ const props = getProps(fiberNode)
227
+ const children = getChildren(fiberNode)
228
+
229
+ return (
230
+ Object.defineProperty({
231
+ type,
232
+ props,
233
+ children
234
+ },
235
+ '$$typeof', {
236
+ value: REACT_COMPONENT_SNAPSHOT
237
+ })
238
+ )
239
+ }
package/src/index.mjs CHANGED
@@ -1,268 +1,132 @@
1
1
  /**
2
+ * @typedef {import('@testing-library/react').RenderResult} RenderType
2
3
  * @typedef {ReactComponentSnapshotTypes.FiberNode} FiberNode
3
4
  */
4
5
 
5
- import {
6
- getComponentNameFromFiber as getType
7
- } from 'react-component-name'
8
-
9
- import {
10
- REACT_COMPONENT_SNAPSHOT
11
- } from './symbols.mjs'
12
-
13
- /**
14
- * @param {unknown} v
15
- * @returns {v is object}
16
- */
17
- function isObject (v) {
18
- return (v || false) instanceof Object && !Array.isArray(v)
19
- }
20
-
21
- /**
22
- * @param {PropertyKey} key
23
- * @returns {boolean}
24
- */
25
- export function isReactFiberKey (key) {
26
- return String(key).startsWith('__reactFiber$')
27
- }
28
-
29
- /**
30
- * @param {PropertyKey} key
31
- * @returns {boolean}
32
- */
33
- export function isReactPropsKey (key) {
34
- return String(key).startsWith('__reactProps$')
35
- }
6
+ import getSnapshot, {
7
+ getFiber
8
+ } from './fiber/index.mjs'
36
9
 
37
10
  /**
38
- * @param {Element | Text | React.Component<any, any, any>} stateNode
39
- * @returns {string | undefined}
11
+ * @param {RenderType} render
12
+ * @returns {Element | null}
40
13
  */
41
- export function getReactFiberKey (stateNode) {
42
- return (
43
- Object.keys(stateNode)
44
- .find(isReactFiberKey)
45
- )
14
+ export function getComponentElement ({
15
+ container: {
16
+ firstElementChild: componentElement = null
17
+ }
18
+ }) {
19
+ return componentElement
46
20
  }
47
21
 
48
22
  /**
49
- * @param {Element | Text | React.Component<any, any, any>} stateNode
50
- * @returns {string | undefined}
23
+ * @param {RenderType} render
24
+ * @returns {Element | null}
51
25
  */
52
- export function getReactPropsKey (stateNode) {
26
+ export function getComponentElementFromRender (render) {
53
27
  return (
54
- Object.keys(stateNode)
55
- .find(isReactPropsKey)
28
+ getComponentElementFromContainerElement(
29
+ getContainerElementFromRender(render)
30
+ )
56
31
  )
57
32
  }
58
33
 
59
34
  /**
60
- * @param {Element | Text | React.Component<any, any, any>} stateNode
61
- * @returns {Record<PropertyKey, unknown> | null}
35
+ * @param {RenderType} render
36
+ * @returns {Element}
62
37
  */
63
- export function getStateNodeFiber (stateNode) {
64
- const key = getReactFiberKey(stateNode)
65
-
66
- if (key) {
67
- const { // @ts-expect-error
68
- [key]: fiber = null
69
- } = stateNode
70
-
71
- return fiber
72
- }
73
-
74
- return null
75
- }
76
-
77
- /**
78
- * @param {React.Component<any, any, any> | Element | Text} stateNode
79
- * @returns {Record<PropertyKey, unknown> | null}
80
- */
81
- export function getStateNodeProps (stateNode) {
82
- const key = getReactPropsKey(stateNode)
83
-
84
- if (key) {
85
- const { // @ts-expect-error
86
- [key]: props = null
87
- } = stateNode
88
-
89
- return props
90
- }
91
-
92
- return null
38
+ export function getContainerElementFromRender ({
39
+ container: containerElement
40
+ }) {
41
+ return containerElement
93
42
  }
94
43
 
95
44
  /**
96
- * @param {{ container: { firstElementChild: Element | null } }} component
45
+ * @param {Element} containerElement
97
46
  * @returns {Element | null}
98
47
  */
99
- export function getComponentElement ({
100
- container: {
101
- firstElementChild: element
102
- }
48
+ function getComponentElementFromContainerElement ({
49
+ firstElementChild: componentElement = null
103
50
  }) {
104
- return element
51
+ return componentElement
105
52
  }
106
53
 
107
54
  /**
108
- * @param {Element} element
109
- * @returns {FiberNode | null}
55
+ * @param {RenderType} render
56
+ * @returns {Record<PropertyKey, unknown> | null}
110
57
  */
111
- function getFiber (element) {
112
- const key = getReactFiberKey(element)
58
+ export function toSnapshotFromRender (render) {
59
+ const element = getComponentElementFromRender(render)
113
60
 
114
- if (key) {
115
- const { // @ts-expect-error
116
- [key]: fiber = null
117
- } = element
61
+ if (element instanceof HTMLElement) {
62
+ const fiberNode = getFiber(element)
118
63
 
119
- return fiber
64
+ if (fiberNode) {
65
+ return (
66
+ getSnapshot(fiberNode)
67
+ )
68
+ }
120
69
  }
121
70
 
122
71
  return null
123
72
  }
124
73
 
125
74
  /**
126
- * @param {FiberNode} fiberNode
127
- * @returns {FiberNode[]}
128
- */
129
- function getSiblings (fiberNode) {
130
- const siblings = []
131
-
132
- let sibling = fiberNode.sibling
133
- while (sibling) {
134
- siblings.push(sibling)
135
- sibling = sibling.sibling
136
- }
137
-
138
- return siblings
139
- }
140
-
141
- /**
142
- * @param {FiberNode} fiberNode
143
- * @returns {Record<PropertyKey, unknown>}
75
+ * @param {Element} containerElement
76
+ * @returns {Record<PropertyKey, unknown> | null}
144
77
  */
145
- function getProps (fiberNode) {
146
- const stateNode = fiberNode.stateNode
78
+ export function toSnapshotFromContainerElement (containerElement) {
79
+ const element = getComponentElementFromContainerElement(containerElement)
147
80
 
148
- /**
149
- * State node may be null on class components
150
- */
151
- if (stateNode) {
152
- const props = getStateNodeProps(stateNode) ?? {}
81
+ if (element instanceof HTMLElement) {
82
+ const fiberNode = getFiber(element)
153
83
 
154
- return (
155
- Object.fromEntries(
156
- Object.entries(props)
157
- .filter(([key]) => key !== 'children')
158
- .filter(([key, value]) => {
159
- if (isObject(value)) return !('current' in value)
160
- return true
161
- })
84
+ if (fiberNode) {
85
+ return (
86
+ getSnapshot(fiberNode)
162
87
  )
163
- )
88
+ }
164
89
  }
165
90
 
166
- return {}
91
+ return null
167
92
  }
168
93
 
169
94
  /**
170
- * @param {FiberNode} fiberNode
171
- * @returns {Array<string | FiberNode>}
95
+ * @param {Element | null} [componentElement]
96
+ * @returns {Record<PropertyKey, unknown> | null}
172
97
  */
173
- function getChildrenOf (fiberNode) {
174
- const child = fiberNode.child
175
-
176
- if (child) {
177
- const siblings = getSiblings(child)
178
-
179
- return [child].concat(siblings).map((child) => {
180
- const stateNode = child.stateNode
181
-
182
- if (stateNode instanceof Text) {
183
- return stateNode.data
184
- }
98
+ export function toSnapshotFromComponentElement (componentElement) {
99
+ if (componentElement instanceof HTMLElement) {
100
+ const fiberNode = getFiber(componentElement)
185
101
 
186
- return child
187
- })
188
- }
189
-
190
- const stateNode = fiberNode.stateNode
191
-
192
- if (stateNode) {
193
- /**
194
- * Get the text as a prop from the instance
195
- */
196
- const {
197
- children
198
- } = getStateNodeProps(stateNode) ?? {}
199
-
200
- if (typeof children === 'string') {
201
- return [children]
102
+ if (fiberNode) {
103
+ return (
104
+ getSnapshot(fiberNode)
105
+ )
202
106
  }
203
107
  }
204
108
 
205
- return []
206
- }
207
-
208
- /**
209
- * @param {FiberNode | string} child
210
- * @returns {string | Record<PropertyKey, unknown>}
211
- */
212
- function toChildren (child) {
213
- if (typeof child === 'string') return child
214
-
215
- return (
216
- transform(child)
217
- )
218
- }
219
-
220
- /**
221
- * @param {FiberNode} fiberNode
222
- * @returns {Array<string | Record<PropertyKey, unknown>>}
223
- */
224
- function getChildren (fiberNode) {
225
- return (
226
- getChildrenOf(fiberNode)
227
- .map(toChildren)
228
- )
229
- }
230
-
231
- /**
232
- * @param {FiberNode} fiberNode
233
- * @returns {Record<PropertyKey, unknown>}
234
- */
235
- function transform (fiberNode) {
236
- const type = getType(fiberNode)
237
- const props = getProps(fiberNode)
238
- const children = getChildren(fiberNode)
239
-
240
- return (
241
- Object.defineProperty({
242
- type,
243
- props,
244
- children
245
- },
246
- '$$typeof', {
247
- value: REACT_COMPONENT_SNAPSHOT
248
- })
249
- )
109
+ return null
250
110
  }
251
111
 
252
112
  /**
253
- * @param {Element | null | undefined} element
113
+ * @param {Element | null} [element]
254
114
  * @returns {Record<PropertyKey, unknown> | null}
255
115
  */
256
- export default function snapshotOf (element) {
116
+ export function snapshotOf (element) {
257
117
  if (element instanceof HTMLElement) {
258
118
  const fiberNode = getFiber(element)
259
119
 
260
120
  if (fiberNode) {
261
121
  return (
262
- transform(fiberNode)
122
+ getSnapshot(fiberNode)
263
123
  )
264
124
  }
265
125
  }
266
126
 
267
127
  return null
268
128
  }
129
+
130
+ export {
131
+ toSnapshotFromRender as toSnapshot
132
+ }
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "NodeNext",
4
- "target": "ESNext",
5
- "allowJs": true,
6
- "checkJs": true,
7
- "noEmit": true,
8
- "strict": true,
9
- "jsx": "react"
10
- },
11
- "include": [
12
- "."
13
- ]
14
- }
File without changes