universal-test-renderer 0.5.0 → 0.7.0
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 +19 -45
- package/dist/index.cjs +139 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +135 -156
- package/dist/index.js.map +1 -1
- package/package.json +28 -25
- package/dist/react-native.cjs +0 -864
- package/dist/react-native.cjs.map +0 -1
- package/dist/react-native.d.cts +0 -10
- package/dist/react-native.d.ts +0 -10
- package/dist/react-native.js +0 -832
- package/dist/react-native.js.map +0 -1
- package/react-native.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# Universal Test Renderer for React
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight, JavaScript-only replacement for the deprecated React Test Renderer.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Why Use It?
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
This renderer is created to be used as replacement for React Test Renderer.
|
|
7
|
+
- **Pure JavaScript Testing** - Test React components in Jest or Vitest without browser or native dependencies
|
|
8
|
+
- **Universal** - Can be used to simulate React Native or any other React renderer
|
|
9
|
+
- **React 19 Ready** - Modern alternative as React Test Renderer is now deprecated
|
|
10
|
+
- **Lightweight** - Minimal dependencies and small bundle size
|
|
11
|
+
- **Type-safe** - Written in TypeScript with full type definitions
|
|
12
|
+
- **Flexible Configuration** - Customizable reconciler options for different use cases
|
|
14
13
|
|
|
15
14
|
## Installation
|
|
16
15
|
|
|
@@ -18,53 +17,28 @@ This renderer is created to be used as replacement for React Test Renderer.
|
|
|
18
17
|
npm install -D universal-test-renderer
|
|
19
18
|
```
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Usage
|
|
20
|
+
## Basic Usage
|
|
24
21
|
|
|
25
22
|
```tsx
|
|
26
23
|
import { act } from "react";
|
|
27
24
|
import { createRoot } from "universal-test-renderer";
|
|
28
25
|
|
|
29
|
-
test("
|
|
26
|
+
test("example", () => {
|
|
30
27
|
const renderer = createRoot();
|
|
31
28
|
act(() => {
|
|
32
29
|
renderer.render(<div>Hello!</div>);
|
|
33
30
|
});
|
|
34
31
|
|
|
35
|
-
expect(renderer.root
|
|
36
|
-
<div>
|
|
37
|
-
|
|
38
|
-
</div>
|
|
39
|
-
`);
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## React Native support
|
|
44
|
-
|
|
45
|
-
This packages includes a version of renderer configured to be compatible with React Native.
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
import { act } from "react";
|
|
49
|
-
import { Text } from "react-native";
|
|
50
|
-
import { createRoot } from "universal-test-renderer/react-native";
|
|
51
|
-
|
|
52
|
-
test("basic renderer usage", () => {
|
|
53
|
-
const renderer = createRoot();
|
|
54
|
-
act(() => {
|
|
55
|
-
renderer.render(<Text>Hello!</Text>);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(renderer.root?.toJSON()).toMatchInlineSnapshot(`
|
|
59
|
-
<Text>
|
|
60
|
-
Hello!
|
|
61
|
-
</Text>
|
|
62
|
-
`);
|
|
32
|
+
expect(renderer.root).toMatchInlineSnapshot(`
|
|
33
|
+
<div>
|
|
34
|
+
Hello!
|
|
35
|
+
</div>
|
|
36
|
+
`);
|
|
63
37
|
});
|
|
64
38
|
```
|
|
65
39
|
|
|
66
|
-
## Differences from React Test Renderer
|
|
40
|
+
## Key Differences from React Test Renderer
|
|
67
41
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
42
|
+
- Works at host component level only (no composite components)
|
|
43
|
+
- More flexible reconciler configuration options
|
|
44
|
+
- Uses `act` from the React package directly
|
package/dist/index.cjs
CHANGED
|
@@ -57,12 +57,124 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
57
57
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
58
58
|
|
|
59
59
|
// src/index.ts
|
|
60
|
-
var
|
|
61
|
-
__export(
|
|
62
|
-
CONTAINER_TYPE: () => CONTAINER_TYPE,
|
|
60
|
+
var index_exports = {};
|
|
61
|
+
__export(index_exports, {
|
|
63
62
|
createRoot: () => createRoot
|
|
64
63
|
});
|
|
65
|
-
module.exports = __toCommonJS(
|
|
64
|
+
module.exports = __toCommonJS(index_exports);
|
|
65
|
+
|
|
66
|
+
// src/renderer.ts
|
|
67
|
+
var import_constants2 = require("react-reconciler/constants");
|
|
68
|
+
|
|
69
|
+
// src/find-all.ts
|
|
70
|
+
function findAll(root, predicate, options) {
|
|
71
|
+
const results = [];
|
|
72
|
+
const matchingDescendants = [];
|
|
73
|
+
root.children.forEach((child) => {
|
|
74
|
+
if (typeof child === "string") {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
matchingDescendants.push(...findAll(child, predicate, options));
|
|
78
|
+
});
|
|
79
|
+
if (
|
|
80
|
+
// When matchDeepestOnly = true: add current element only if no descendants match
|
|
81
|
+
(!(options == null ? void 0 : options.matchDeepestOnly) || matchingDescendants.length === 0) && predicate(root)
|
|
82
|
+
) {
|
|
83
|
+
results.push(root);
|
|
84
|
+
}
|
|
85
|
+
results.push(...matchingDescendants);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/render-to-json.ts
|
|
90
|
+
function renderToJson(instance) {
|
|
91
|
+
if (instance.isHidden) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
switch (instance.tag) {
|
|
95
|
+
case "TEXT":
|
|
96
|
+
return instance.text;
|
|
97
|
+
case "INSTANCE": {
|
|
98
|
+
const _a = instance.props, { children } = _a, props = __objRest(_a, ["children"]);
|
|
99
|
+
let renderedChildren = null;
|
|
100
|
+
if (instance.children.length) {
|
|
101
|
+
for (let i = 0; i < instance.children.length; i++) {
|
|
102
|
+
const renderedChild = renderToJson(instance.children[i]);
|
|
103
|
+
if (renderedChild !== null) {
|
|
104
|
+
if (renderedChildren === null) {
|
|
105
|
+
renderedChildren = [renderedChild];
|
|
106
|
+
} else {
|
|
107
|
+
renderedChildren.push(renderedChild);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const result = {
|
|
113
|
+
type: instance.type,
|
|
114
|
+
props,
|
|
115
|
+
children: renderedChildren,
|
|
116
|
+
$$typeof: /* @__PURE__ */ Symbol.for("react.test.json")
|
|
117
|
+
};
|
|
118
|
+
Object.defineProperty(result, "$$typeof", {
|
|
119
|
+
value: /* @__PURE__ */ Symbol.for("react.test.json")
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/host-element.ts
|
|
127
|
+
var instanceMap = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
var HostElement = class _HostElement {
|
|
129
|
+
constructor(instance) {
|
|
130
|
+
this.instance = instance;
|
|
131
|
+
}
|
|
132
|
+
get type() {
|
|
133
|
+
return this.instance.type;
|
|
134
|
+
}
|
|
135
|
+
get props() {
|
|
136
|
+
const _a = this.instance.props, { children } = _a, restProps = __objRest(_a, ["children"]);
|
|
137
|
+
return restProps;
|
|
138
|
+
}
|
|
139
|
+
get children() {
|
|
140
|
+
const result = this.instance.children.filter((child) => !child.isHidden).map((child) => getHostNodeForInstance(child));
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
get parent() {
|
|
144
|
+
const parentInstance = this.instance.parent;
|
|
145
|
+
if (parentInstance == null || parentInstance.tag === "CONTAINER") {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return _HostElement.fromInstance(parentInstance);
|
|
149
|
+
}
|
|
150
|
+
get unstable_fiber() {
|
|
151
|
+
return this.instance.unstable_fiber;
|
|
152
|
+
}
|
|
153
|
+
get $$typeof() {
|
|
154
|
+
return /* @__PURE__ */ Symbol.for("react.test.json");
|
|
155
|
+
}
|
|
156
|
+
toJSON() {
|
|
157
|
+
return renderToJson(this.instance);
|
|
158
|
+
}
|
|
159
|
+
/** @internal */
|
|
160
|
+
static fromInstance(instance) {
|
|
161
|
+
const hostElement = instanceMap.get(instance);
|
|
162
|
+
if (hostElement) {
|
|
163
|
+
return hostElement;
|
|
164
|
+
}
|
|
165
|
+
const result = new _HostElement(instance);
|
|
166
|
+
instanceMap.set(instance, result);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function getHostNodeForInstance(instance) {
|
|
171
|
+
switch (instance.tag) {
|
|
172
|
+
case "TEXT":
|
|
173
|
+
return instance.text;
|
|
174
|
+
case "INSTANCE":
|
|
175
|
+
return HostElement.fromInstance(instance);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
66
178
|
|
|
67
179
|
// src/reconciler.ts
|
|
68
180
|
var import_react_reconciler = __toESM(require("react-reconciler"), 1);
|
|
@@ -157,7 +269,7 @@ var hostConfig = {
|
|
|
157
269
|
children: [],
|
|
158
270
|
parent: null,
|
|
159
271
|
rootContainer,
|
|
160
|
-
internalHandle
|
|
272
|
+
unstable_fiber: internalHandle
|
|
161
273
|
};
|
|
162
274
|
},
|
|
163
275
|
/**
|
|
@@ -275,9 +387,7 @@ var hostConfig = {
|
|
|
275
387
|
*/
|
|
276
388
|
getChildHostContext(parentHostContext, type) {
|
|
277
389
|
var _a;
|
|
278
|
-
const isInsideText = Boolean(
|
|
279
|
-
(_a = parentHostContext.config.textComponents) == null ? void 0 : _a.includes(type)
|
|
280
|
-
);
|
|
390
|
+
const isInsideText = Boolean((_a = parentHostContext.config.textComponents) == null ? void 0 : _a.includes(type));
|
|
281
391
|
return __spreadProps(__spreadValues({}, parentHostContext), { type, isInsideText });
|
|
282
392
|
},
|
|
283
393
|
/**
|
|
@@ -382,7 +492,7 @@ var hostConfig = {
|
|
|
382
492
|
getInstanceFromNode(node) {
|
|
383
493
|
const instance = nodeToInstanceMap.get(node);
|
|
384
494
|
if (instance !== void 0) {
|
|
385
|
-
return instance.
|
|
495
|
+
return instance.unstable_fiber;
|
|
386
496
|
}
|
|
387
497
|
return null;
|
|
388
498
|
},
|
|
@@ -511,7 +621,7 @@ var hostConfig = {
|
|
|
511
621
|
commitUpdate(instance, type, _prevProps, nextProps, internalHandle) {
|
|
512
622
|
instance.type = type;
|
|
513
623
|
instance.props = nextProps;
|
|
514
|
-
instance.
|
|
624
|
+
instance.unstable_fiber = internalHandle;
|
|
515
625
|
},
|
|
516
626
|
/**
|
|
517
627
|
* #### `hideInstance(instance)`
|
|
@@ -645,139 +755,7 @@ function removeChild(parentInstance, child) {
|
|
|
645
755
|
child.parent = null;
|
|
646
756
|
}
|
|
647
757
|
|
|
648
|
-
// src/constants.ts
|
|
649
|
-
var CONTAINER_TYPE = "CONTAINER";
|
|
650
|
-
|
|
651
|
-
// src/render-to-json.ts
|
|
652
|
-
function renderToJson(instance) {
|
|
653
|
-
if (`isHidden` in instance && instance.isHidden) {
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
switch (instance.tag) {
|
|
657
|
-
case "TEXT":
|
|
658
|
-
return instance.text;
|
|
659
|
-
case "INSTANCE": {
|
|
660
|
-
const _a = instance.props, { children } = _a, props = __objRest(_a, ["children"]);
|
|
661
|
-
let renderedChildren = null;
|
|
662
|
-
if (instance.children.length) {
|
|
663
|
-
for (let i = 0; i < instance.children.length; i++) {
|
|
664
|
-
const renderedChild = renderToJson(instance.children[i]);
|
|
665
|
-
if (renderedChild !== null) {
|
|
666
|
-
if (renderedChildren === null) {
|
|
667
|
-
renderedChildren = [renderedChild];
|
|
668
|
-
} else {
|
|
669
|
-
renderedChildren.push(renderedChild);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
const result = {
|
|
675
|
-
type: instance.type,
|
|
676
|
-
props,
|
|
677
|
-
children: renderedChildren,
|
|
678
|
-
$$typeof: Symbol.for("react.test.json")
|
|
679
|
-
};
|
|
680
|
-
Object.defineProperty(result, "$$typeof", {
|
|
681
|
-
value: Symbol.for("react.test.json")
|
|
682
|
-
});
|
|
683
|
-
return result;
|
|
684
|
-
}
|
|
685
|
-
case "CONTAINER": {
|
|
686
|
-
let renderedChildren = null;
|
|
687
|
-
if (instance.children.length) {
|
|
688
|
-
for (let i = 0; i < instance.children.length; i++) {
|
|
689
|
-
const renderedChild = renderToJson(instance.children[i]);
|
|
690
|
-
if (renderedChild !== null) {
|
|
691
|
-
if (renderedChildren === null) {
|
|
692
|
-
renderedChildren = [renderedChild];
|
|
693
|
-
} else {
|
|
694
|
-
renderedChildren.push(renderedChild);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
const result = {
|
|
700
|
-
type: CONTAINER_TYPE,
|
|
701
|
-
props: {},
|
|
702
|
-
children: renderedChildren,
|
|
703
|
-
$$typeof: Symbol.for("react.test.json")
|
|
704
|
-
};
|
|
705
|
-
Object.defineProperty(result, "$$typeof", {
|
|
706
|
-
value: Symbol.for("react.test.json")
|
|
707
|
-
});
|
|
708
|
-
return result;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/host-element.ts
|
|
714
|
-
var instanceToHostElementMap = /* @__PURE__ */ new WeakMap();
|
|
715
|
-
var HostElement = class _HostElement {
|
|
716
|
-
constructor(instance) {
|
|
717
|
-
this.instance = instance;
|
|
718
|
-
}
|
|
719
|
-
get type() {
|
|
720
|
-
return this.instance.tag === "INSTANCE" ? this.instance.type : CONTAINER_TYPE;
|
|
721
|
-
}
|
|
722
|
-
get props() {
|
|
723
|
-
if (this.instance.tag === "CONTAINER") {
|
|
724
|
-
return {};
|
|
725
|
-
}
|
|
726
|
-
const _a = this.instance.props, { children } = _a, restProps = __objRest(_a, ["children"]);
|
|
727
|
-
return restProps;
|
|
728
|
-
}
|
|
729
|
-
get children() {
|
|
730
|
-
const result = this.instance.children.filter((child) => !child.isHidden).map((child) => _HostElement.fromInstance(child));
|
|
731
|
-
return result;
|
|
732
|
-
}
|
|
733
|
-
get parent() {
|
|
734
|
-
const parentInstance = this.instance.parent;
|
|
735
|
-
if (parentInstance == null) {
|
|
736
|
-
return null;
|
|
737
|
-
}
|
|
738
|
-
switch (parentInstance.tag) {
|
|
739
|
-
case "INSTANCE":
|
|
740
|
-
return _HostElement.fromInstance(parentInstance);
|
|
741
|
-
case "CONTAINER":
|
|
742
|
-
return _HostElement.fromContainer(parentInstance);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
get $$typeof() {
|
|
746
|
-
return Symbol.for("react.test.json");
|
|
747
|
-
}
|
|
748
|
-
toJSON() {
|
|
749
|
-
return renderToJson(this.instance);
|
|
750
|
-
}
|
|
751
|
-
/** @internal */
|
|
752
|
-
static fromContainer(container) {
|
|
753
|
-
const hostElement = instanceToHostElementMap.get(container);
|
|
754
|
-
if (hostElement) {
|
|
755
|
-
return hostElement;
|
|
756
|
-
}
|
|
757
|
-
const result = new _HostElement(container);
|
|
758
|
-
instanceToHostElementMap.set(container, result);
|
|
759
|
-
return result;
|
|
760
|
-
}
|
|
761
|
-
/** @internal */
|
|
762
|
-
static fromInstance(instance) {
|
|
763
|
-
switch (instance.tag) {
|
|
764
|
-
case "TEXT":
|
|
765
|
-
return instance.text;
|
|
766
|
-
case "INSTANCE": {
|
|
767
|
-
const hostElement = instanceToHostElementMap.get(instance);
|
|
768
|
-
if (hostElement) {
|
|
769
|
-
return hostElement;
|
|
770
|
-
}
|
|
771
|
-
const result = new _HostElement(instance);
|
|
772
|
-
instanceToHostElementMap.set(instance, result);
|
|
773
|
-
return result;
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
};
|
|
778
|
-
|
|
779
758
|
// src/renderer.ts
|
|
780
|
-
var import_constants4 = require("react-reconciler/constants");
|
|
781
759
|
function createRoot(options) {
|
|
782
760
|
var _a;
|
|
783
761
|
let container = {
|
|
@@ -786,12 +764,12 @@ function createRoot(options) {
|
|
|
786
764
|
parent: null,
|
|
787
765
|
config: {
|
|
788
766
|
textComponents: options == null ? void 0 : options.textComponents,
|
|
789
|
-
createNodeMock: (_a = options == null ? void 0 : options.createNodeMock) != null ? _a : () => ({})
|
|
767
|
+
createNodeMock: (_a = options == null ? void 0 : options.createNodeMock) != null ? _a : (() => ({}))
|
|
790
768
|
}
|
|
791
769
|
};
|
|
792
770
|
let containerFiber = TestReconciler.createContainer(
|
|
793
771
|
container,
|
|
794
|
-
|
|
772
|
+
import_constants2.ConcurrentRoot,
|
|
795
773
|
null,
|
|
796
774
|
// hydration callbacks
|
|
797
775
|
false,
|
|
@@ -824,33 +802,33 @@ function createRoot(options) {
|
|
|
824
802
|
container = null;
|
|
825
803
|
containerFiber = null;
|
|
826
804
|
};
|
|
805
|
+
const getRoot = () => {
|
|
806
|
+
if (containerFiber == null || container == null) {
|
|
807
|
+
throw new Error("Can't access .root on unmounted test renderer");
|
|
808
|
+
}
|
|
809
|
+
if (container.children.length === 0) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
const firstChild = container.children[0];
|
|
813
|
+
if (firstChild.tag === "TEXT") {
|
|
814
|
+
throw new Error("Cannot render text as root element");
|
|
815
|
+
}
|
|
816
|
+
return HostElement.fromInstance(firstChild);
|
|
817
|
+
};
|
|
827
818
|
return {
|
|
828
819
|
render,
|
|
829
820
|
unmount,
|
|
830
821
|
get root() {
|
|
831
|
-
|
|
832
|
-
throw new Error("Can't access .root on unmounted test renderer");
|
|
833
|
-
}
|
|
834
|
-
if (container.children.length === 0) {
|
|
835
|
-
return null;
|
|
836
|
-
}
|
|
837
|
-
const root = HostElement.fromInstance(container.children[0]);
|
|
838
|
-
if (typeof root === "string") {
|
|
839
|
-
throw new Error("Cannot render string as root element");
|
|
840
|
-
}
|
|
841
|
-
return root;
|
|
822
|
+
return getRoot();
|
|
842
823
|
},
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
return HostElement.fromContainer(container);
|
|
824
|
+
findAll: (predicate, options2) => {
|
|
825
|
+
const root = getRoot();
|
|
826
|
+
return root != null ? findAll(root, predicate, options2) : [];
|
|
848
827
|
}
|
|
849
828
|
};
|
|
850
829
|
}
|
|
851
830
|
// Annotate the CommonJS export names for ESM import in node:
|
|
852
831
|
0 && (module.exports = {
|
|
853
|
-
CONTAINER_TYPE,
|
|
854
832
|
createRoot
|
|
855
833
|
});
|
|
856
834
|
//# sourceMappingURL=index.cjs.map
|