Skip to content

Commit e15c3f9

Browse files
committed
Merge branch 'srmagura-async-transform'
2 parents b05f76b + 36a79f3 commit e15c3f9

File tree

4 files changed

+127
-23
lines changed

4 files changed

+127
-23
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
"react live"
9191
],
9292
"jest": {
93+
"testEnvironment": "jsdom",
94+
"resetMocks": true,
9395
"rootDir": "./src",
9496
"testURL": "http://localhost/"
9597
},

src/components/Live/LiveProvider.js

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,54 @@ function LiveProvider({
1919
element: undefined,
2020
});
2121

22-
function transpile(newCode) {
23-
// Transpilation arguments
24-
const input = {
25-
code: transformCode ? transformCode(newCode) : newCode,
26-
scope,
22+
function transpileAsync(newCode) {
23+
const errorCallback = (error) => {
24+
setState({ error: error.toString(), element: undefined });
2725
};
2826

29-
const errorCallback = (error) =>
30-
setState({ error: error.toString(), element: undefined });
27+
// - transformCode may be synchronous or asynchronous.
28+
// - transformCode may throw an exception or return a rejected promise, e.g.
29+
// if newCode is invalid and cannot be transformed.
30+
// - Not using async-await to since it requires targeting ES 2017 or
31+
// importing regenerator-runtime... in the next major version of
32+
// react-live, should target ES 2017+
33+
try {
34+
const transformResult = transformCode ? transformCode(newCode) : newCode;
3135

32-
const renderElement = (element) => setState({ error: undefined, element });
36+
return Promise.resolve(transformResult)
37+
.then((transformedCode) => {
38+
const renderElement = (element) =>
39+
setState({ error: undefined, element });
3340

34-
try {
35-
if (noInline) {
36-
setState({ error: undefined, element: null }); // Reset output for async (no inline) evaluation
37-
renderElementAsync(input, renderElement, errorCallback);
38-
} else {
39-
renderElement(generateElement(input, errorCallback));
40-
}
41-
} catch (error) {
42-
errorCallback(error);
41+
// Transpilation arguments
42+
const input = {
43+
code: transformedCode,
44+
scope,
45+
};
46+
47+
if (noInline) {
48+
setState({ error: undefined, element: null }); // Reset output for async (no inline) evaluation
49+
renderElementAsync(input, renderElement, errorCallback);
50+
} else {
51+
renderElement(generateElement(input, errorCallback));
52+
}
53+
})
54+
.catch(errorCallback);
55+
} catch (e) {
56+
errorCallback(e);
57+
return Promise.resolve();
4358
}
4459
}
4560

61+
const onError = (error) => setState({ error: error.toString() });
62+
4663
useEffect(() => {
47-
transpile(code);
64+
transpileAsync(code).catch(onError);
4865
}, [code, scope, noInline, transformCode]);
4966

50-
const onChange = (newCode) => transpile(newCode);
51-
52-
const onError = (error) => setState({ error: error.toString() });
67+
const onChange = (newCode) => {
68+
transpileAsync(newCode).catch(onError);
69+
};
5370

5471
return (
5572
<LiveContext.Provider
@@ -76,7 +93,7 @@ LiveProvider.propTypes = {
7693
noInline: PropTypes.bool,
7794
scope: PropTypes.object,
7895
theme: PropTypes.object,
79-
transformCode: PropTypes.node,
96+
transformCode: PropTypes.func,
8097
};
8198

8299
LiveProvider.defaultProps = {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { useContext } from "react";
2+
import { act } from "react-dom/test-utils";
3+
import { renderElementAsync } from "../../utils/transpile";
4+
import { render } from "../../utils/test/renderer";
5+
import LiveProvider from "./LiveProvider";
6+
import LiveContext from "./LiveContext";
7+
8+
jest.mock("../../utils/transpile");
9+
10+
function waitAsync() {
11+
return act(() => new Promise((resolve) => setTimeout(resolve, 0)));
12+
}
13+
14+
it("applies a synchronous transformCode function", () => {
15+
function transformCode(code) {
16+
return `render(<div>${code}</div>)`;
17+
}
18+
19+
render(<LiveProvider code="hello" noInline transformCode={transformCode} />);
20+
21+
return waitAsync().then(() => {
22+
expect(renderElementAsync).toHaveBeenCalledTimes(1);
23+
expect(renderElementAsync.mock.calls[0][0].code).toBe(
24+
"render(<div>hello</div>)"
25+
);
26+
});
27+
});
28+
29+
it("applies an asynchronous transformCode function", () => {
30+
function transformCode(code) {
31+
return Promise.resolve(`render(<div>${code}</div>)`);
32+
}
33+
34+
render(<LiveProvider code="hello" noInline transformCode={transformCode} />);
35+
36+
return waitAsync().then(() => {
37+
expect(renderElementAsync).toHaveBeenCalledTimes(1);
38+
expect(renderElementAsync.mock.calls[0][0].code).toBe(
39+
"render(<div>hello</div>)"
40+
);
41+
});
42+
});
43+
44+
function ErrorRenderer() {
45+
const { error } = useContext(LiveContext);
46+
return <div data-testid="handledError">{error?.message}</div>;
47+
}
48+
49+
it("catches errors from a synchronous transformCode function", () => {
50+
function transformCode() {
51+
throw new Error("testError");
52+
}
53+
54+
const wrapper = render(
55+
<LiveProvider code="hello" noInline transformCode={transformCode}>
56+
<ErrorRenderer />
57+
</LiveProvider>
58+
);
59+
60+
return waitAsync().then(() => {
61+
expect(renderElementAsync).not.toHaveBeenCalled();
62+
63+
const handledErrorWrapper = wrapper.find('[data-testid="handledError"]');
64+
expect(handledErrorWrapper.text()).toBe("testError");
65+
});
66+
});
67+
68+
it("catches errors from an asynchronous transformCode function", () => {
69+
function transformCode() {
70+
return Promise.reject(new Error("testError"));
71+
}
72+
73+
const wrapper = render(
74+
<LiveProvider code="hello" noInline transformCode={transformCode}>
75+
<ErrorRenderer />
76+
</LiveProvider>
77+
);
78+
79+
return waitAsync().then(() => {
80+
expect(renderElementAsync).not.toHaveBeenCalled();
81+
82+
const handledErrorWrapper = wrapper.find('[data-testid="handledError"]');
83+
expect(handledErrorWrapper.text()).toBe("testError");
84+
});
85+
});

typings/react-live.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type LiveProviderProps = Omit<DivProps, 'scope'> & {
1313
scope?: { [key: string]: any };
1414
code?: string;
1515
noInline?: boolean;
16-
transformCode?: (code: string) => string;
16+
transformCode?: (code: string) => (string | Promise<string>);
1717
language?: Language;
1818
disabled?: boolean;
1919
theme?: PrismTheme;

0 commit comments

Comments
 (0)