Skip to content

Commit 07d4846

Browse files
feat: add prepTestComponent (#255)
* feat: add prepTestComponent This function handles creating and mounting React component. It then returns a DOM element with the mounted component, so that tests can inspect it. * refactor: always use globalThis
1 parent 4d81259 commit 07d4846

File tree

6 files changed

+937
-82
lines changed

6 files changed

+937
-82
lines changed

.babelrc

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
{
22
"presets": [
33
"@babel/preset-typescript",
4-
["@babel/preset-env", {
5-
"targets": {
6-
"node": "current"
4+
[
5+
"@babel/preset-env",
6+
{
7+
"targets": {
8+
"node": "current"
9+
}
710
}
8-
}]
9-
11+
],
12+
["@babel/preset-react", { "runtime": "automatic" }]
1013
]
11-
}
14+
}

docs/curriculum-helpers.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ let regEx = functionRegex("myFunc", ["arg1"], { capture: true });
6666
let match = "myFunc = arg1 => arg1; console.log();\n // captured, unfortunately".match(regEx);
6767
match[1] // "myFunc = arg1 => arg1; console.log();\n // captured, unfortunately"
6868
```
69+
70+
## prepTestComponent
71+
72+
Renders a React component into a DOM element and returns a Promise containing the DOM element. The arguments are, respectively, the component to render and an (optional) object containing the props to pass to the component.
73+
74+
```javascript
75+
import { SomeComponent } from "./SomeComponent";
76+
const element = await prepTestComponent(SomeComponent, { someProp: "someValue" });
77+
element.querySelector("h1").textContent === "Some Value";
78+
```
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom/client";
3+
14
import cssTestValues from "../__fixtures__/curriculum-helper-css";
25
import htmlTestValues from "../__fixtures__/curriculum-helpers-html";
36
import jsTestValues from "../__fixtures__/curriculum-helpers-javascript";
@@ -392,3 +395,51 @@ describe("functionRegex", () => {
392395
expect(code.match(funcRE)![0]).toBe("const naomi = (love) => ");
393396
});
394397
});
398+
399+
describe("prepTestComponent", () => {
400+
let MyComponent;
401+
beforeEach(() => {
402+
MyComponent = (props) => <main>{props.text}</main>;
403+
404+
globalThis.React = React;
405+
globalThis.ReactDOM = ReactDOM;
406+
});
407+
408+
afterEach(() => {
409+
delete globalThis.React;
410+
delete globalThis.ReactDOM;
411+
jest.restoreAllMocks();
412+
});
413+
414+
it("should return an HTML element", async () => {
415+
const { prepTestComponent } = helper;
416+
417+
const el = await prepTestComponent(MyComponent);
418+
419+
expect(el).toBeInstanceOf(HTMLElement);
420+
});
421+
422+
it("should render a component", async () => {
423+
const { prepTestComponent } = helper;
424+
425+
const el = await prepTestComponent(MyComponent);
426+
427+
expect(el.innerHTML).toBe("<main></main>");
428+
});
429+
430+
it("should render a component with props", async () => {
431+
const { prepTestComponent } = helper;
432+
433+
const el = await prepTestComponent(MyComponent, { text: "Hello" });
434+
435+
expect(el.innerHTML).toBe("<main>Hello</main>");
436+
});
437+
438+
it("should not log any errors to the console", async () => {
439+
const { prepTestComponent } = helper;
440+
const spy = jest.spyOn(console, "error").mockImplementation();
441+
442+
await prepTestComponent(MyComponent);
443+
expect(spy).not.toHaveBeenCalled();
444+
});
445+
});

lib/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { strip } from "./strip";
22
import astHelpers from "../python/py_helpers.py";
33

4+
declare global {
5+
// eslint-disable-next-line no-var
6+
var IS_REACT_ACT_ENVIRONMENT: boolean;
7+
}
8+
49
/**
510
* The `RandomMocker` class provides functionality to mock and restore the global `Math.random` function.
611
* It replaces the default random number generator with a deterministic pseudo-random number generator.
@@ -177,6 +182,23 @@ const getIsDeclaredAfter = (styleRule: CSSStyleRule) => (selector: string) => {
177182
return currPosition > prevPosition;
178183
};
179184

185+
export async function prepTestComponent(
186+
component: unknown,
187+
props?: Record<string, unknown>
188+
) {
189+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
190+
const testDiv = document.createElement("div");
191+
// @ts-expect-error the React version is determined at runtime so we can't define the types here
192+
const createdElement = globalThis.React?.createElement(component, props);
193+
194+
// @ts-expect-error or here
195+
await globalThis.React?.act(async () => {
196+
// @ts-expect-error Same for ReactDOM as for React
197+
globalThis.ReactDOM?.createRoot(testDiv).render(createdElement);
198+
});
199+
return testDiv;
200+
}
201+
180202
export const python = {
181203
astHelpers,
182204
getDef(code: string, functionName: string) {

0 commit comments

Comments
 (0)