+
+
@@ -86,15 +86,15 @@ exports[`Markdown should render code blocks without escaping 1`] = `
exports[`Markdown should render emphasis and strong text 1`] = `
-
+
this text is
-
+
strong
-
+
and this is
-
+
emphasized
@@ -103,44 +103,44 @@ exports[`Markdown should render emphasis and strong text 1`] = `
exports[`Markdown should render headings with generated ids 1`] = `
-
+
one
-
+
two
-
+
three
-
+
four
-
+
five
-
+
six
@@ -149,9 +149,9 @@ exports[`Markdown should render headings with generated ids 1`] = `
`;
exports[`Markdown should render inline code with escaping 1`] = `
-
+
Foo
-
+
<bar>
baz
@@ -159,10 +159,10 @@ exports[`Markdown should render inline code with escaping 1`] = `
`;
exports[`Markdown should render links 1`] = `
-
+
a
link
@@ -170,39 +170,39 @@ exports[`Markdown should render links 1`] = `
`;
exports[`Markdown should render mixed nested lists 1`] = `
-
-
+
+
list 1
-
+
list 2
-
-
+
+
Sub-list
-
+
Sub-list
-
+
Sub-list
-
+
list 3
`;
exports[`Markdown should render ordered lists 1`] = `
-
-
+
+
list
-
+
item
-
+
three
@@ -210,31 +210,31 @@ exports[`Markdown should render ordered lists 1`] = `
exports[`Markdown should render paragraphs 1`] = `
-
+
a paragraph
-
+
another paragraph
`;
exports[`Markdown should render pre-formatted text 1`] = `
-
+
this is preformatted
so is this
`;
exports[`Markdown should render unordered lists 1`] = `
-
-
+
+
list
-
+
item
-
+
three
diff --git a/src/client/rsg-components/Message/Message.spec.tsx b/src/client/rsg-components/Message/Message.spec.tsx
index a19006ca9..5e44b5cb1 100644
--- a/src/client/rsg-components/Message/Message.spec.tsx
+++ b/src/client/rsg-components/Message/Message.spec.tsx
@@ -1,17 +1,19 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { MessageRenderer } from './MessageRenderer';
it('renderer should render message', () => {
const message = 'Hello *world*!';
- const actual = shallow({message} );
+ const renderer = createRenderer();
+ renderer.render({message} );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('renderer should render message for array', () => {
const messages = ['Hello *world*!', 'Foo _bar_'];
- const actual = shallow({messages} );
+ const renderer = createRenderer();
+ renderer.render({messages} );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Message/MessageRenderer.tsx b/src/client/rsg-components/Message/MessageRenderer.tsx
index 9f9970d0c..2c31100a1 100644
--- a/src/client/rsg-components/Message/MessageRenderer.tsx
+++ b/src/client/rsg-components/Message/MessageRenderer.tsx
@@ -32,7 +32,7 @@ export const MessageRenderer: React.FunctionComponent = ({ classes
MessageRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- children: PropTypes.node.isRequired,
+ children: PropTypes.any.isRequired,
};
export default Styled(styles)(MessageRenderer);
diff --git a/src/client/rsg-components/Methods/Methods.spec.tsx b/src/client/rsg-components/Methods/Methods.spec.tsx
index 3e81957bb..df0319818 100644
--- a/src/client/rsg-components/Methods/Methods.spec.tsx
+++ b/src/client/rsg-components/Methods/Methods.spec.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { parse, MethodDescriptor } from 'react-docgen';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import MethodsRenderer, { columns } from './MethodsRenderer';
// Test renderers with clean readable snapshot diffs
@@ -37,15 +37,19 @@ function render(methods: string[]) {
undefined,
{ filename: '' }
);
+ const renderer = createRenderer();
if (Array.isArray(parsed) || !parsed.methods) {
- return shallow(
);
+ renderer.render(
);
+ } else {
+ renderer.render( );
}
- return shallow( );
+ return renderer.getRenderOutput();
}
describe('MethodsRenderer', () => {
it('should render a table', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{
/>
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
});
@@ -86,7 +90,8 @@ describe('PropsRenderer', () => {
});
it('should render JsDoc tags', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{
/>
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render deprecated JsDoc tags', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{
/>
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
});
diff --git a/src/client/rsg-components/Methods/MethodsRenderer.tsx b/src/client/rsg-components/Methods/MethodsRenderer.tsx
index 8ace2ebe1..89f0754cd 100644
--- a/src/client/rsg-components/Methods/MethodsRenderer.tsx
+++ b/src/client/rsg-components/Methods/MethodsRenderer.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import Markdown from 'rsg-components/Markdown';
import Argument from 'rsg-components/Argument';
import Arguments from 'rsg-components/Arguments';
@@ -40,16 +39,4 @@ const MethodsRenderer: React.FunctionComponent<{ methods: MethodDescriptor[] }>
);
-MethodsRenderer.propTypes = {
- methods: PropTypes.arrayOf(
- PropTypes.shape({
- name: PropTypes.string.isRequired,
- description: PropTypes.string,
- returns: PropTypes.object,
- params: PropTypes.array,
- tags: PropTypes.object,
- }).isRequired
- ).isRequired,
-};
-
export default MethodsRenderer;
diff --git a/src/client/rsg-components/Methods/__snapshots__/Methods.spec.tsx.snap b/src/client/rsg-components/Methods/__snapshots__/Methods.spec.tsx.snap
index 280cdd370..b703801a3 100644
--- a/src/client/rsg-components/Methods/__snapshots__/Methods.spec.tsx.snap
+++ b/src/client/rsg-components/Methods/__snapshots__/Methods.spec.tsx.snap
@@ -34,28 +34,20 @@ exports[`MethodsRenderer should render a table 1`] = `
exports[`PropsRenderer should render JsDoc tags 1`] = `
-
-
+
+
Foo()
-
+
-
+
-
-
+
+
Foo()
-
+
-
+
-
-
+
+
method()
-
+
-
+
-
-
+
+
method()
-
+
-
+
-
-
+
+
method()
-
+
-
+
{
- const actual = shallow(Foo );
+ const renderer = createRenderer();
+ renderer.render(Foo );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('renderer should render deprecated argument name', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Foo
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Name/NameRenderer.tsx b/src/client/rsg-components/Name/NameRenderer.tsx
index c558f0f50..aab0c8a4d 100644
--- a/src/client/rsg-components/Name/NameRenderer.tsx
+++ b/src/client/rsg-components/Name/NameRenderer.tsx
@@ -34,7 +34,7 @@ export const NameRenderer: React.FunctionComponent = ({
NameRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- children: PropTypes.node.isRequired,
+ children: PropTypes.any.isRequired,
deprecated: PropTypes.bool,
};
diff --git a/src/client/rsg-components/NotFound/NotFound.spec.tsx b/src/client/rsg-components/NotFound/NotFound.spec.tsx
index b96353902..7440da190 100644
--- a/src/client/rsg-components/NotFound/NotFound.spec.tsx
+++ b/src/client/rsg-components/NotFound/NotFound.spec.tsx
@@ -1,9 +1,10 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { NotFoundRenderer } from './NotFoundRenderer';
it('renderer should render not found message', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Para/Para.spec.tsx b/src/client/rsg-components/Para/Para.spec.tsx
index e2411c3b3..684802aca 100644
--- a/src/client/rsg-components/Para/Para.spec.tsx
+++ b/src/client/rsg-components/Para/Para.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { ParaRenderer, styles } from './ParaRenderer';
const props = {
@@ -7,17 +7,19 @@ const props = {
};
it('should render paragraph as a ', () => {
- const actual = shallow(
Pizza );
+ const renderer = createRenderer();
+ renderer.render(
Pizza );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render paragraph as a
', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Para/ParaRenderer.tsx b/src/client/rsg-components/Para/ParaRenderer.tsx
index 87677bbb4..e9b5b869c 100644
--- a/src/client/rsg-components/Para/ParaRenderer.tsx
+++ b/src/client/rsg-components/Para/ParaRenderer.tsx
@@ -32,7 +32,7 @@ export const ParaRenderer: React.FunctionComponent = ({
ParaRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
semantic: PropTypes.oneOf(['p']),
- children: PropTypes.node.isRequired,
+ children: PropTypes.any.isRequired,
};
export default Styled(styles)(ParaRenderer);
diff --git a/src/client/rsg-components/Pathline/Pathline.spec.tsx b/src/client/rsg-components/Pathline/Pathline.spec.tsx
index 0090f4ac9..91bed3326 100644
--- a/src/client/rsg-components/Pathline/Pathline.spec.tsx
+++ b/src/client/rsg-components/Pathline/Pathline.spec.tsx
@@ -1,6 +1,7 @@
+import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import copy from 'clipboard-copy';
-import { shallow, mount } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { PathlineRenderer, styles } from './PathlineRenderer';
jest.mock('clipboard-copy');
@@ -11,12 +12,13 @@ const props = {
};
it('renderer should a path line', () => {
- const actual = shallow({pathline} );
- expect(actual).toMatchSnapshot();
+ const renderer = createRenderer();
+ renderer.render({pathline} );
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
test('should copy text on click', () => {
- const actual = mount({pathline} );
- actual.find('button').simulate('click');
+ const { getByRole } = render({pathline} );
+ fireEvent.click(getByRole('button'));
expect(copy).toBeCalledWith(pathline);
});
diff --git a/src/client/rsg-components/Pathline/PathlineRenderer.tsx b/src/client/rsg-components/Pathline/PathlineRenderer.tsx
index 4e427c3b2..1f9aa4eb5 100644
--- a/src/client/rsg-components/Pathline/PathlineRenderer.tsx
+++ b/src/client/rsg-components/Pathline/PathlineRenderer.tsx
@@ -17,10 +17,11 @@ export const styles = ({ space, fontFamily, fontSize, color }: Rsg.Theme) => ({
},
});
-export const PathlineRenderer: React.FunctionComponent = ({
- classes,
- children,
-}) => {
+interface Props extends JssInjectedProps {
+ children?: React.ReactNode;
+}
+
+export const PathlineRenderer = ({ classes, children }: Props) => {
return (
{children}
diff --git a/src/client/rsg-components/Playground/Playground.tsx b/src/client/rsg-components/Playground/Playground.tsx
index 87007d2af..d0226b27a 100644
--- a/src/client/rsg-components/Playground/Playground.tsx
+++ b/src/client/rsg-components/Playground/Playground.tsx
@@ -1,11 +1,10 @@
import React, { Component } from 'react';
-import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import Preview from 'rsg-components/Preview';
import Para from 'rsg-components/Para';
import Slot from 'rsg-components/Slot';
import PlaygroundRenderer from 'rsg-components/Playground/PlaygroundRenderer';
-import Context from 'rsg-components/Context';
+import Context, { StyleGuideContextContents } from 'rsg-components/Context';
import { EXAMPLE_TAB_CODE_EDITOR } from '../slots';
import { DisplayModes, ExampleModes } from '../../consts';
@@ -31,26 +30,13 @@ interface PlaygroundState {
}
class Playground extends Component
{
- public static propTypes = {
- code: PropTypes.string.isRequired,
- evalInContext: PropTypes.func.isRequired,
- index: PropTypes.number.isRequired,
- name: PropTypes.string.isRequired,
- exampleMode: PropTypes.string.isRequired,
- settings: PropTypes.object,
- };
-
- public static defaultProps = {
- settings: {},
- };
-
public static contextType = Context;
- private handleChange = debounce(code => {
+ private handleChange = debounce((code) => {
this.setState({
code,
});
- }, this.context.config.previewDelay);
+ }, (this.context as StyleGuideContextContents).config.previewDelay);
public state: PlaygroundState = {
code: this.props.code,
@@ -80,7 +66,7 @@ class Playground extends Component {
}
private handleTabChange = (name: string) => {
- this.setState(state => ({
+ this.setState((state) => ({
activeTab: state.activeTab !== name ? name : undefined,
}));
};
@@ -88,7 +74,7 @@ class Playground extends Component {
public render() {
const { code, activeTab } = this.state;
const { evalInContext, index, name, settings, exampleMode } = this.props;
- const { displayMode } = this.context;
+ const { displayMode } = this.context as StyleGuideContextContents;
const isExampleHidden = exampleMode === ExampleModes.hide;
const isEditorHidden = settings.noeditor || isExampleHidden;
const preview = ;
diff --git a/src/client/rsg-components/Playground/PlaygroundRenderer.tsx b/src/client/rsg-components/Playground/PlaygroundRenderer.tsx
index ef9249d81..13d51ce14 100644
--- a/src/client/rsg-components/Playground/PlaygroundRenderer.tsx
+++ b/src/client/rsg-components/Playground/PlaygroundRenderer.tsx
@@ -84,11 +84,11 @@ PlaygroundRenderer.propTypes = {
exampleIndex: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
padded: PropTypes.bool.isRequired,
- preview: PropTypes.node.isRequired,
+ preview: PropTypes.any.isRequired,
previewProps: PropTypes.object.isRequired,
- tabButtons: PropTypes.node.isRequired,
- tabBody: PropTypes.node.isRequired,
- toolbar: PropTypes.node.isRequired,
+ tabButtons: PropTypes.any.isRequired,
+ tabBody: PropTypes.any.isRequired,
+ toolbar: PropTypes.any.isRequired,
};
export default Styled(styles)(PlaygroundRenderer);
diff --git a/src/client/rsg-components/PlaygroundError/PlaygroundError.spec.tsx b/src/client/rsg-components/PlaygroundError/PlaygroundError.spec.tsx
index 87cab1a4e..9816ad4ba 100644
--- a/src/client/rsg-components/PlaygroundError/PlaygroundError.spec.tsx
+++ b/src/client/rsg-components/PlaygroundError/PlaygroundError.spec.tsx
@@ -1,10 +1,11 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { PlaygroundErrorRenderer } from './PlaygroundErrorRenderer';
it('renderer should render message', () => {
const message = 'Hello *world*!';
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Preview/Preview.spec.tsx b/src/client/rsg-components/Preview/Preview.spec.tsx
index 849d67eae..ad823e99b 100644
--- a/src/client/rsg-components/Preview/Preview.spec.tsx
+++ b/src/client/rsg-components/Preview/Preview.spec.tsx
@@ -1,6 +1,5 @@
import React from 'react';
-import { mount } from 'enzyme';
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
import Preview from '.';
import Context, { StyleGuideContextContents } from '../Context';
@@ -32,7 +31,7 @@ afterEach(() => {
console.clear = console$clear;
});
-it('should unmount Wrapper component', () => {
+it('should unmount Wrapper component', async () => {
const { unmount, getByTestId } = render(
@@ -43,7 +42,7 @@ it('should unmount Wrapper component', () => {
expect(node.innerHTML).toMatch(' expect(node.innerHTML).toBe(''));
});
it('should not fail when Wrapper wasn’t mounted', () => {
@@ -59,7 +58,7 @@ it('should not fail when Wrapper wasn’t mounted', () => {
const node = getByTestId('mountNode');
expect(
- consoleError.mock.calls.find(call =>
+ consoleError.mock.calls.find((call) =>
call[0].toString().includes('ReferenceError: pizza is not defined')
)
).toBeTruthy();
@@ -123,7 +122,7 @@ it('should handle errors', () => {
);
expect(
- consoleError.mock.calls.find(call =>
+ consoleError.mock.calls.find((call) =>
call[0].toString().includes('SyntaxError: Unexpected token')
)
).toBeTruthy();
@@ -131,7 +130,7 @@ it('should handle errors', () => {
it('should not clear console on initial mount', () => {
console.clear = jest.fn();
- mount(
+ render(
@@ -141,7 +140,7 @@ it('should not clear console on initial mount', () => {
it('should clear console on second mount', () => {
console.clear = jest.fn();
- mount(
+ render(
diff --git a/src/client/rsg-components/Preview/Preview.tsx b/src/client/rsg-components/Preview/Preview.tsx
index 71555f8be..3f64fa978 100644
--- a/src/client/rsg-components/Preview/Preview.tsx
+++ b/src/client/rsg-components/Preview/Preview.tsx
@@ -1,9 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import ReactDOM from 'react-dom';
import PlaygroundError from 'rsg-components/PlaygroundError';
import ReactExample from 'rsg-components/ReactExample';
-import Context from 'rsg-components/Context';
+import Context, { StyleGuideContextContents } from 'rsg-components/Context';
+import { createRoot, Root } from 'react-dom/client';
const improveErrorMessage = (message: string) =>
message.replace(
@@ -28,6 +28,8 @@ export default class Preview extends Component {
public static contextType = Context;
private mountNode: Element | null = null;
+ private reactRoot: Root | null = null;
+ private timeoutId: number | null = null;
public state: PreviewState = {
error: null,
@@ -36,7 +38,7 @@ export default class Preview extends Component {
public componentDidMount() {
// Clear console after hot reload, do not clear on the first load
// to keep any warnings
- if (this.context.codeRevision > 0) {
+ if ((this.context as StyleGuideContextContents).codeRevision > 0) {
// eslint-disable-next-line no-console
console.clear();
}
@@ -59,9 +61,17 @@ export default class Preview extends Component {
}
public unmountPreview() {
- if (this.mountNode) {
- ReactDOM.unmountComponentAtNode(this.mountNode);
+ const self = this;
+ if (self.timeoutId) {
+ clearTimeout(self.timeoutId);
}
+ const id = setTimeout(() => {
+ if (self.reactRoot) {
+ self.reactRoot.unmount();
+ self.reactRoot = null;
+ }
+ });
+ self.timeoutId = id;
}
private executeCode() {
@@ -79,16 +89,26 @@ export default class Preview extends Component {
code={code}
evalInContext={this.props.evalInContext}
onError={this.handleError}
- compilerConfig={this.context.config.compilerConfig}
+ compilerConfig={(this.context as StyleGuideContextContents).config.compilerConfig}
/>
);
+ /* istanbul ignore next */
window.requestAnimationFrame(() => {
- // this.unmountPreview();
+ if (!this.mountNode) {
+ return;
+ }
try {
- ReactDOM.render(wrappedComponent, this.mountNode);
+ if (this.reactRoot === null) {
+ this.reactRoot = createRoot(this.mountNode);
+ this.reactRoot.render(wrappedComponent);
+ } else {
+ this.reactRoot.render(wrappedComponent);
+ }
} catch (err) {
- this.handleError(err);
+ if (err instanceof Error) {
+ this.handleError(err);
+ }
}
});
}
@@ -103,11 +123,18 @@ export default class Preview extends Component {
console.error(err); // eslint-disable-line no-console
};
+ private callbackRef = (ref: HTMLDivElement | null) => {
+ this.mountNode = ref;
+ if (!this.reactRoot && ref) {
+ this.reactRoot = createRoot(ref);
+ }
+ };
+
public render() {
const { error } = this.state;
return (
<>
- (this.mountNode = ref)} />
+
{error &&
}
>
);
diff --git a/src/client/rsg-components/ReactComponent/ReactComponent.tsx b/src/client/rsg-components/ReactComponent/ReactComponent.tsx
index 5c57893d2..b22ac4cbe 100644
--- a/src/client/rsg-components/ReactComponent/ReactComponent.tsx
+++ b/src/client/rsg-components/ReactComponent/ReactComponent.tsx
@@ -6,7 +6,7 @@ import JsDoc from 'rsg-components/JsDoc';
import Markdown from 'rsg-components/Markdown';
import Slot from 'rsg-components/Slot';
import ReactComponentRenderer from 'rsg-components/ReactComponent/ReactComponentRenderer';
-import Context from 'rsg-components/Context';
+import Context, { StyleGuideContextContents } from 'rsg-components/Context';
import ExamplePlaceholderDefault from 'rsg-components/ExamplePlaceholder';
import { DOCS_TAB_USAGE } from '../slots';
import { DisplayModes, UsageModes } from '../../consts';
@@ -41,7 +41,7 @@ export default class ReactComponent extends Component
{
- this.setState(state => ({
+ this.setState((state) => ({
activeTab: state.activeTab !== name ? name : undefined,
}));
};
@@ -51,7 +51,7 @@ export default class ReactComponent extends Component any) =>
@@ -8,27 +10,31 @@ const evalInContext = (a: string): (() => any) =>
new Function('require', 'const React = require("react");' + a).bind(null, require);
it('should render code', () => {
- const actual = shallow(
+ const testRenderer = createRenderer();
+ testRenderer.render(
OK'} evalInContext={evalInContext} onError={noop} />
);
- expect(actual).toMatchSnapshot();
+ expect(testRenderer.getRenderOutput()).toMatchSnapshot();
});
it('should wrap code in Fragment when it starts with <', () => {
- const actual = mount(
+ const actual = renderer.create(
);
- expect(actual.html()).toMatchSnapshot();
+ expect(actual.toJSON()).toMatchSnapshot();
});
it('should handle errors', () => {
const onError = jest.fn();
- shallow( );
+ const testRenderer = createRenderer();
+ testRenderer.render(
+
+ );
expect(onError).toHaveBeenCalledTimes(1);
});
@@ -38,9 +44,11 @@ it('should set initial state with hooks', () => {
const [count, setCount] = React.useState(0);
{count}
`;
- const actual = mount( );
+ const { getByRole } = render(
+
+ );
- expect(actual.find('button').text()).toEqual('0');
+ expect(getByRole('button').textContent).toEqual('0');
});
it('should update state with hooks', () => {
@@ -48,8 +56,10 @@ it('should update state with hooks', () => {
const [count, setCount] = React.useState(0);
setCount(count+1)}>{count}
`;
- const actual = mount( );
- actual.find('button').simulate('click');
+ const { getByRole } = render(
+
+ );
+ fireEvent.click(getByRole('button'));
- expect(actual.find('button').text()).toEqual('1');
+ expect(getByRole('button').textContent).toEqual('1');
});
diff --git a/src/client/rsg-components/ReactExample/__snapshots__/ReactExample.spec.tsx.snap b/src/client/rsg-components/ReactExample/__snapshots__/ReactExample.spec.tsx.snap
index 6b3e52b41..2552cfeb7 100644
--- a/src/client/rsg-components/ReactExample/__snapshots__/ReactExample.spec.tsx.snap
+++ b/src/client/rsg-components/ReactExample/__snapshots__/ReactExample.spec.tsx.snap
@@ -10,9 +10,7 @@ exports[`should render code 1`] = `
exports[`should wrap code in Fragment when it starts with < 1`] = `
-
-
-
-
+
+
`;
diff --git a/src/client/rsg-components/Ribbon/RibbonRenderer.tsx b/src/client/rsg-components/Ribbon/RibbonRenderer.tsx
index 3faef9880..82f3c57f7 100644
--- a/src/client/rsg-components/Ribbon/RibbonRenderer.tsx
+++ b/src/client/rsg-components/Ribbon/RibbonRenderer.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import Styled, { JssInjectedProps } from 'rsg-components/Styled';
import * as Rsg from '../../../typings';
@@ -37,7 +36,11 @@ interface RibbonProps extends JssInjectedProps {
text?: string;
}
-export const RibbonRenderer: React.FunctionComponent = ({ classes, url, text }) => {
+export const RibbonRenderer: React.FunctionComponent = ({
+ classes,
+ url,
+ text = 'Fork me on GitHub',
+}) => {
return (
@@ -47,14 +50,4 @@ export const RibbonRenderer: React.FunctionComponent = ({ classes,
);
};
-RibbonRenderer.defaultProps = {
- text: 'Fork me on GitHub',
-};
-
-RibbonRenderer.propTypes = {
- classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- url: PropTypes.string.isRequired,
- text: PropTypes.string,
-};
-
export default Styled(styles)(RibbonRenderer);
diff --git a/src/client/rsg-components/Section/SectionRenderer.tsx b/src/client/rsg-components/Section/SectionRenderer.tsx
index 5eb55d970..3b0323979 100644
--- a/src/client/rsg-components/Section/SectionRenderer.tsx
+++ b/src/client/rsg-components/Section/SectionRenderer.tsx
@@ -24,7 +24,7 @@ interface SectionRendererProps extends JssInjectedProps {
[prop: string]: any;
}
-export const SectionRenderer: React.FunctionComponent = allProps => {
+export const SectionRenderer: React.FunctionComponent = (allProps) => {
const {
classes,
name,
@@ -63,9 +63,9 @@ SectionRenderer.propTypes = {
name: PropTypes.string,
description: PropTypes.string,
slug: PropTypes.string.isRequired,
- content: PropTypes.node,
- components: PropTypes.node,
- sections: PropTypes.node,
+ content: PropTypes.any,
+ components: PropTypes.any,
+ sections: PropTypes.any,
isolated: PropTypes.bool,
depth: PropTypes.number.isRequired,
pagePerSection: PropTypes.bool,
diff --git a/src/client/rsg-components/SectionHeading/SectionHeading.spec.tsx b/src/client/rsg-components/SectionHeading/SectionHeading.spec.tsx
index 7a6a01b76..73c541eb8 100644
--- a/src/client/rsg-components/SectionHeading/SectionHeading.spec.tsx
+++ b/src/client/rsg-components/SectionHeading/SectionHeading.spec.tsx
@@ -1,5 +1,6 @@
import React from 'react';
-import { shallow, mount } from 'enzyme';
+import renderer from 'react-test-renderer';
+import { createRenderer } from 'react-test-renderer/shallow';
import SectionHeading from './index';
import SectionHeadingRenderer from './SectionHeadingRenderer';
@@ -7,7 +8,8 @@ describe('SectionHeading', () => {
const FakeToolbar = () => Fake toolbar
;
test('should forward slot properties to the toolbar', () => {
- const actual = shallow(
+ const testRenderer = createRenderer();
+ testRenderer.render(
{
);
- expect(actual).toMatchSnapshot();
+ expect(testRenderer.getRenderOutput()).toMatchSnapshot();
});
test('render a section heading', () => {
- const actual = mount(
+ const actual = renderer.create(
}>
A Section
);
- expect(actual.find('h2')).toMatchSnapshot();
+ expect(actual.toJSON()).toMatchSnapshot();
});
test('render a deprecated section heading', () => {
- const actual = mount(
+ const actual = renderer.create(
{
);
- expect(actual.find('h2')).toMatchSnapshot();
+ expect(actual.toJSON()).toMatchSnapshot();
});
test('prevent the heading level from exceeding the maximum allowed by the Heading component', () => {
- const actual = mount(
+ const actual = renderer.create(
}>
A Section
);
- expect(actual.find('h6')).toHaveLength(1);
+ expect(actual.toJSON()).toMatchSnapshot();
});
});
diff --git a/src/client/rsg-components/SectionHeading/SectionHeading.tsx b/src/client/rsg-components/SectionHeading/SectionHeading.tsx
index 1a76c16f4..d29b4665b 100644
--- a/src/client/rsg-components/SectionHeading/SectionHeading.tsx
+++ b/src/client/rsg-components/SectionHeading/SectionHeading.tsx
@@ -35,7 +35,7 @@ const SectionHeading: React.FunctionComponent = ({
};
SectionHeading.propTypes = {
- children: PropTypes.node,
+ children: PropTypes.any,
id: PropTypes.string.isRequired,
slotName: PropTypes.string.isRequired,
slotProps: PropTypes.any.isRequired,
diff --git a/src/client/rsg-components/SectionHeading/SectionHeadingRenderer.tsx b/src/client/rsg-components/SectionHeading/SectionHeadingRenderer.tsx
index 46bcd8911..09ffa74c7 100644
--- a/src/client/rsg-components/SectionHeading/SectionHeadingRenderer.tsx
+++ b/src/client/rsg-components/SectionHeading/SectionHeadingRenderer.tsx
@@ -67,8 +67,8 @@ const SectionHeadingRenderer: React.FunctionComponent
+
+
+
+`;
+
exports[`SectionHeading render a deprecated section heading 1`] = `
-
+
- A Section
-
-
+
+ Fake toolbar
+
+
+
`;
exports[`SectionHeading render a section heading 1`] = `
-
+
- A Section
-
-
+
+ Fake toolbar
+
+
+
`;
exports[`SectionHeading should forward slot properties to the toolbar 1`] = `
diff --git a/src/client/rsg-components/Sections/Sections.spec.tsx b/src/client/rsg-components/Sections/Sections.spec.tsx
index 3039bc087..44b1443e9 100644
--- a/src/client/rsg-components/Sections/Sections.spec.tsx
+++ b/src/client/rsg-components/Sections/Sections.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import noop from 'lodash/noop';
import Section from '../Section';
import Sections from './Sections';
@@ -42,13 +42,15 @@ const sections = [
] as any;
it('should render component renderer', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('render should render styled component', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
@@ -56,11 +58,12 @@ it('render should render styled component', () => {
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('render should render component', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
@@ -68,5 +71,5 @@ it('render should render component', () => {
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Sections/SectionsRenderer.tsx b/src/client/rsg-components/Sections/SectionsRenderer.tsx
index dcf47b68b..c5ea74d75 100644
--- a/src/client/rsg-components/Sections/SectionsRenderer.tsx
+++ b/src/client/rsg-components/Sections/SectionsRenderer.tsx
@@ -20,7 +20,7 @@ export const SectionsRenderer: React.FunctionComponent =
SectionsRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- children: PropTypes.node,
+ children: PropTypes.any,
};
export default Styled(styles)(SectionsRenderer);
diff --git a/src/client/rsg-components/Sections/__snapshots__/Sections.spec.tsx.snap b/src/client/rsg-components/Sections/__snapshots__/Sections.spec.tsx.snap
index 64e700fe0..b642556d8 100644
--- a/src/client/rsg-components/Sections/__snapshots__/Sections.spec.tsx.snap
+++ b/src/client/rsg-components/Sections/__snapshots__/Sections.spec.tsx.snap
@@ -4,15 +4,12 @@ exports[`render should render component 1`] = `
- OK
-,
+ "content": "OK ",
"evalInContext": [Function],
"type": "code",
},
@@ -23,7 +20,6 @@ exports[`render should render component 1`] = `
/>
- OK
-,
+ "content": "OK ",
"evalInContext": [Function],
"type": "code",
},
@@ -87,7 +79,6 @@ exports[`render should render styled component 1`] = `
/>
- OK
-,
+ "content": "OK ",
"evalInContext": [Function],
"type": "code",
},
@@ -145,7 +132,6 @@ exports[`should render component renderer 1`] = `
/>
{
- public static propTypes = {
- codeRevision: PropTypes.number.isRequired,
- cssRevision: PropTypes.string.isRequired,
- config: PropTypes.object.isRequired,
- slots: PropTypes.object.isRequired,
- sections: PropTypes.array.isRequired,
- welcomeScreen: PropTypes.bool,
- patterns: PropTypes.array,
- displayMode: PropTypes.string,
- allSections: PropTypes.array.isRequired,
- pagePerSection: PropTypes.bool,
- };
- public static defaultProps = {
- displayMode: DisplayModes.all,
- };
-
public state = {
error: false,
info: null,
@@ -85,7 +68,7 @@ export default class StyleGuide extends Component ({
+export const styles = ({
+ space,
+ color,
+ fontFamily,
+ fontSize,
+ buttonTextTransform,
+}: Rsg.Theme): Styles => ({
button: {
padding: [[space[1], 0]],
fontFamily: fontFamily.base,
@@ -48,7 +54,7 @@ export const TabButtonRenderer: React.FunctionComponent = ({
name,
className,
onClick,
- active,
+ active = false,
children,
}) => {
const classNames = cx(classes.button, className, {
@@ -68,16 +74,4 @@ export const TabButtonRenderer: React.FunctionComponent = ({
);
};
-TabButtonRenderer.propTypes = {
- classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- name: PropTypes.string.isRequired,
- className: PropTypes.string,
- onClick: PropTypes.func.isRequired,
- active: PropTypes.bool,
- children: PropTypes.node.isRequired,
-};
-TabButtonRenderer.defaultProps = {
- active: false,
-};
-
export default Styled(styles)(TabButtonRenderer);
diff --git a/src/client/rsg-components/Table/Table.spec.tsx b/src/client/rsg-components/Table/Table.spec.tsx
index 5971e9bc8..84b18624b 100644
--- a/src/client/rsg-components/Table/Table.spec.tsx
+++ b/src/client/rsg-components/Table/Table.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { TableRenderer, styles } from './TableRenderer';
const columns = [
@@ -25,7 +25,8 @@ const props = {
};
it('should render a table', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Table/__snapshots__/Table.spec.tsx.snap b/src/client/rsg-components/Table/__snapshots__/Table.spec.tsx.snap
index 127715c8c..6b2bdeb78 100644
--- a/src/client/rsg-components/Table/__snapshots__/Table.spec.tsx.snap
+++ b/src/client/rsg-components/Table/__snapshots__/Table.spec.tsx.snap
@@ -10,25 +10,20 @@ exports[`should render a table 1`] = `
Name
Type
-
+
name:
@@ -37,7 +32,6 @@ exports[`should render a table 1`] = `
type:
@@ -45,12 +39,9 @@ exports[`should render a table 1`] = `
-
+
name:
@@ -59,7 +50,6 @@ exports[`should render a table 1`] = `
type:
@@ -67,12 +57,9 @@ exports[`should render a table 1`] = `
-
+
name:
@@ -81,7 +68,6 @@ exports[`should render a table 1`] = `
type:
diff --git a/src/client/rsg-components/TableOfContents/TableOfContents.spec.tsx b/src/client/rsg-components/TableOfContents/TableOfContents.spec.tsx
index 7f3c05c54..8bfddfcc9 100644
--- a/src/client/rsg-components/TableOfContents/TableOfContents.spec.tsx
+++ b/src/client/rsg-components/TableOfContents/TableOfContents.spec.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import TableOfContents from './TableOfContents';
import { TableOfContentsRenderer } from './TableOfContentsRenderer';
import Context from '../Context';
@@ -105,7 +105,7 @@ it('should call a callback when input value changed', () => {
const onSearchTermChange = jest.fn();
const searchTerm = 'foo';
const newSearchTerm = 'bar';
- const actual = shallow(
+ const { getByRole } = render(
{
);
- actual.find('input').simulate('change', {
- target: {
- value: newSearchTerm,
- },
- });
+ fireEvent.change(getByRole('textbox'), { target: { value: newSearchTerm } });
expect(onSearchTermChange).toBeCalledWith(newSearchTerm);
});
it('should render content of subsections of a section that has no components', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
);
- expect(actual.find('ComponentsList').prop('items')).toMatchInlineSnapshot(`
- Array [
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "initialOpen": true,
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- },
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "initialOpen": true,
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- },
- ]
+ expect(renderer.getRenderOutput()).toMatchInlineSnapshot(`
+
+
+
`);
});
it('should render components of a single top section as root', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual.find('ComponentsList').prop('items')).toMatchInlineSnapshot(`
- Array [
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "href": "#button",
- "initialOpen": true,
- "name": "Button",
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- "slug": "button",
- "visibleName": "Button",
- },
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "href": "#input",
- "initialOpen": true,
- "name": "Input",
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- "slug": "input",
- "visibleName": "Input",
- },
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "href": "#textarea",
- "initialOpen": true,
- "name": "Textarea",
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- "slug": "textarea",
- "visibleName": "Textarea",
- },
- ]
- `);
+ expect(renderer.getRenderOutput()).toMatchInlineSnapshot(`
+
+
+
+`);
});
it('should render as the link will open in a new window only if external presents as true', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
);
- expect(actual.find('ComponentsList').prop('items')).toMatchInlineSnapshot(`
- Array [
- Object {
- "components": Array [],
- "content": undefined,
- "forcedOpen": false,
- "heading": false,
- "href": "http://example.com",
- "initialOpen": true,
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- },
- Object {
- "components": Array [],
- "content": undefined,
- "external": true,
- "forcedOpen": false,
- "heading": false,
- "href": "http://example.com",
- "initialOpen": true,
- "sections": Array [],
- "selected": false,
- "shouldOpenInNewTab": false,
- },
- ]
- `);
+ expect(renderer.getRenderOutput()).toMatchInlineSnapshot(`
+
+
+
+`);
});
/**
diff --git a/src/client/rsg-components/TableOfContents/TableOfContents.tsx b/src/client/rsg-components/TableOfContents/TableOfContents.tsx
index 166e1b6d3..496d911fc 100644
--- a/src/client/rsg-components/TableOfContents/TableOfContents.tsx
+++ b/src/client/rsg-components/TableOfContents/TableOfContents.tsx
@@ -1,5 +1,4 @@
import React, { Component } from 'react';
-import PropTypes from 'prop-types';
import ComponentsList from 'rsg-components/ComponentsList';
import TableOfContentsRenderer from 'rsg-components/TableOfContents/TableOfContentsRenderer';
import filterSectionsByName from '../../utils/filterSectionsByName';
@@ -10,21 +9,10 @@ interface TableOfContentsProps {
sections: Rsg.Section[];
useRouterLinks?: boolean;
tocMode?: string;
- loc: { hash: string; pathname: string };
+ loc?: { hash: string; pathname: string };
}
export default class TableOfContents extends Component {
- public static propTypes = {
- sections: PropTypes.array.isRequired,
- useRouterLinks: PropTypes.bool,
- tocMode: PropTypes.string,
- loc: PropTypes.object,
- };
-
- public static defaultProps = {
- loc: window.location,
- };
-
public state = {
searchTerm: '',
};
@@ -36,7 +24,7 @@ export default class TableOfContents extends Component {
useHashId = false
): { content: React.ReactElement; containsSelected: boolean } {
// Match selected component in both basic routing and pagePerSection routing.
- const { hash, pathname } = this.props.loc;
+ const { hash, pathname } = this.props.loc ?? window.location;
const windowHash = pathname + (useRouterLinks ? hash : getHash(hash));
let childrenContainSelected = false;
@@ -91,7 +79,9 @@ export default class TableOfContents extends Component {
? sections[0].sections
: sections[0].components
: sections;
- const filtered = firstLevel ? filterSectionsByName(firstLevel, searchTerm) : firstLevel || [];
+ const filtered = firstLevel
+ ? filterSectionsByName(firstLevel as Rsg.TOCItem[], searchTerm)
+ : firstLevel || [];
return this.renderLevel(filtered, useRouterLinks).content;
}
diff --git a/src/client/rsg-components/TableOfContents/TableOfContentsRenderer.tsx b/src/client/rsg-components/TableOfContents/TableOfContentsRenderer.tsx
index bd5882252..5e79f96ae 100644
--- a/src/client/rsg-components/TableOfContents/TableOfContentsRenderer.tsx
+++ b/src/client/rsg-components/TableOfContents/TableOfContentsRenderer.tsx
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { Styles } from 'jss';
import Styled, { JssInjectedProps } from 'rsg-components/Styled';
import * as Rsg from '../../../typings';
-const styles = ({ space, color, fontFamily, fontSize, borderRadius }: Rsg.Theme) => ({
+const styles = ({ space, color, fontFamily, fontSize, borderRadius }: Rsg.Theme): Styles => ({
root: {
fontFamily: fontFamily.base,
},
@@ -58,7 +59,7 @@ export const TableOfContentsRenderer: React.FunctionComponent onSearchTermChange(event.target.value)}
+ onChange={(event) => onSearchTermChange(event.target.value)}
/>
{children}
@@ -70,7 +71,7 @@ export const TableOfContentsRenderer: React.FunctionComponent {
it('should render text', () => {
- const actual = shallow(Pizza );
+ const renderer = createRenderer();
+ renderer.render(Pizza );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render underlined text', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render sized text', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render colored text', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render text with a semantic tag and styles', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render text with a title', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
Pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
});
diff --git a/src/client/rsg-components/Text/TextRenderer.tsx b/src/client/rsg-components/Text/TextRenderer.tsx
index 16a1eb378..e965b186d 100644
--- a/src/client/rsg-components/Text/TextRenderer.tsx
+++ b/src/client/rsg-components/Text/TextRenderer.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import cx from 'clsx';
import Styled, { JssInjectedProps } from 'rsg-components/Styled';
import * as Rsg from '../../../typings';
@@ -49,9 +48,9 @@ export interface TextProps extends JssInjectedProps {
export const TextRenderer: React.FunctionComponent = ({
classes,
semantic,
- size,
- color,
- underlined,
+ size = 'inherit',
+ color = 'base',
+ underlined = false,
children,
...props
}) => {
@@ -68,19 +67,4 @@ export const TextRenderer: React.FunctionComponent = ({
);
};
-TextRenderer.propTypes = {
- classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- semantic: PropTypes.oneOf(['em', 'strong']),
- size: PropTypes.oneOf(['inherit', 'small', 'base', 'text']),
- color: PropTypes.oneOf(['base', 'light']),
- underlined: PropTypes.bool,
- children: PropTypes.node.isRequired,
-};
-
-TextRenderer.defaultProps = {
- size: 'inherit',
- color: 'base',
- underlined: false,
-};
-
export default Styled(styles)(TextRenderer);
diff --git a/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx b/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
index 7039178c6..58e99b6a2 100644
--- a/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
+++ b/src/client/rsg-components/ToolbarButton/ToolbarButton.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { ToolbarButtonRenderer, styles } from './ToolbarButtonRenderer';
const props = {
@@ -8,51 +8,56 @@ const props = {
};
it('should render a button', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{}}>
pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render a link', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
pizza
);
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should pass a class name to a button', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{}} className="foo-class">
pizza
);
- expect(actual.prop('className')).toBe('button foo-class');
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should pass a class name to a link', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
pizza
);
- expect(actual.prop('className')).toBe('button foo-class');
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render a button with small styles', () => {
- const actual = shallow(
+ const renderer = createRenderer();
+ renderer.render(
{}} small>
butterbrot
);
- expect(actual.prop('className')).toBe('button isSmall');
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/ToolbarButton/ToolbarButtonRenderer.tsx b/src/client/rsg-components/ToolbarButton/ToolbarButtonRenderer.tsx
index 56d89337a..01639b808 100644
--- a/src/client/rsg-components/ToolbarButton/ToolbarButtonRenderer.tsx
+++ b/src/client/rsg-components/ToolbarButton/ToolbarButtonRenderer.tsx
@@ -87,7 +87,7 @@ ToolbarButtonRenderer.propTypes = {
title: PropTypes.string,
small: PropTypes.bool,
testId: PropTypes.string,
- children: PropTypes.node,
+ children: PropTypes.any,
};
export default Styled(styles)(ToolbarButtonRenderer);
diff --git a/src/client/rsg-components/ToolbarButton/__snapshots__/ToolbarButton.spec.tsx.snap b/src/client/rsg-components/ToolbarButton/__snapshots__/ToolbarButton.spec.tsx.snap
index 9474b36dd..873af79ea 100644
--- a/src/client/rsg-components/ToolbarButton/__snapshots__/ToolbarButton.spec.tsx.snap
+++ b/src/client/rsg-components/ToolbarButton/__snapshots__/ToolbarButton.spec.tsx.snap
@@ -1,5 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should pass a class name to a button 1`] = `
+
+ pizza
+
+`;
+
+exports[`should pass a class name to a link 1`] = `
+
+ pizza
+
+`;
+
exports[`should render a button 1`] = `
`;
+exports[`should render a button with small styles 1`] = `
+
+ butterbrot
+
+`;
+
exports[`should render a link 1`] = `
{
- const actual = shallow(Array );
+ const renderer = createRenderer();
+ renderer.render(Array );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Type/TypeRenderer.tsx b/src/client/rsg-components/Type/TypeRenderer.tsx
index 64f5cd8b4..22de5e3d3 100644
--- a/src/client/rsg-components/Type/TypeRenderer.tsx
+++ b/src/client/rsg-components/Type/TypeRenderer.tsx
@@ -21,7 +21,7 @@ export const TypeRenderer: React.FunctionComponent = ({ classes, chil
TypeRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- children: PropTypes.node.isRequired,
+ children: PropTypes.any.isRequired,
};
export default Styled(styles)(TypeRenderer);
diff --git a/src/client/rsg-components/Usage/Usage.spec.tsx b/src/client/rsg-components/Usage/Usage.spec.tsx
index b230bc3b7..7f34113b6 100644
--- a/src/client/rsg-components/Usage/Usage.spec.tsx
+++ b/src/client/rsg-components/Usage/Usage.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { PropTypeDescriptor, MethodDescriptor } from 'react-docgen';
import Usage from './Usage';
@@ -28,20 +28,23 @@ const methods: MethodDescriptor[] = [
describe('Usage', () => {
it('should render props table', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render methods table', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should render nothing without props and methods', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual.getElement()).toBe(null);
+ expect(renderer.getRenderOutput()).toBe(null);
});
});
diff --git a/src/client/rsg-components/Usage/Usage.tsx b/src/client/rsg-components/Usage/Usage.tsx
index ae6468e6a..db7358451 100644
--- a/src/client/rsg-components/Usage/Usage.tsx
+++ b/src/client/rsg-components/Usage/Usage.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import PropTypes from 'prop-types';
import { MethodDescriptor } from 'react-docgen';
import { PropDescriptor } from 'rsg-components/Props/util';
import Props from 'rsg-components/Props';
@@ -24,11 +23,4 @@ const Usage: React.FunctionComponent<{
);
};
-Usage.propTypes = {
- props: PropTypes.shape({
- props: PropTypes.array,
- methods: PropTypes.array,
- }).isRequired,
-};
-
export default Usage;
diff --git a/src/client/rsg-components/Version/Version.spec.tsx b/src/client/rsg-components/Version/Version.spec.tsx
index 974f300ca..a835e1615 100644
--- a/src/client/rsg-components/Version/Version.spec.tsx
+++ b/src/client/rsg-components/Version/Version.spec.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { render } from 'enzyme';
+import renderer from 'react-test-renderer';
import VersionRenderer from './VersionRenderer';
it('renderer should render version', () => {
- const actual = render(1.2.3-a );
+ const actual = renderer.create(1.2.3-a );
- expect(actual).toMatchSnapshot();
+ expect(actual.toJSON()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Version/VersionRenderer.tsx b/src/client/rsg-components/Version/VersionRenderer.tsx
index 8d53aae3c..01283ddf8 100644
--- a/src/client/rsg-components/Version/VersionRenderer.tsx
+++ b/src/client/rsg-components/Version/VersionRenderer.tsx
@@ -27,7 +27,7 @@ export const VersionRenderer: React.FunctionComponent = ({ classes
VersionRenderer.propTypes = {
classes: PropTypes.objectOf(PropTypes.string.isRequired).isRequired,
- children: PropTypes.node,
+ children: PropTypes.any,
};
export default Styled(styles)(VersionRenderer);
diff --git a/src/client/rsg-components/Version/__snapshots__/Version.spec.tsx.snap b/src/client/rsg-components/Version/__snapshots__/Version.spec.tsx.snap
index fa39be1ec..294e30cde 100644
--- a/src/client/rsg-components/Version/__snapshots__/Version.spec.tsx.snap
+++ b/src/client/rsg-components/Version/__snapshots__/Version.spec.tsx.snap
@@ -3,7 +3,7 @@
exports[`renderer should render version 1`] = `
1.2.3-a
diff --git a/src/client/rsg-components/Welcome/Welcome.spec.tsx b/src/client/rsg-components/Welcome/Welcome.spec.tsx
index fc2b88482..62fa8f4c1 100644
--- a/src/client/rsg-components/Welcome/Welcome.spec.tsx
+++ b/src/client/rsg-components/Welcome/Welcome.spec.tsx
@@ -1,9 +1,10 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import { WelcomeRenderer } from './WelcomeRenderer';
it('renderer should render welcome screen', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/Wrapper/Wrapper.spec.tsx b/src/client/rsg-components/Wrapper/Wrapper.spec.tsx
index ba5069385..b5ab3c490 100644
--- a/src/client/rsg-components/Wrapper/Wrapper.spec.tsx
+++ b/src/client/rsg-components/Wrapper/Wrapper.spec.tsx
@@ -1,23 +1,25 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import Wrapper from './Wrapper';
it('should render children', () => {
const children = Hello ;
- const actual = shallow( {}}>{children} );
+ const renderer = createRenderer();
+ renderer.render( {}}>{children} );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should call onError handler when React invokes error handler', () => {
const onError = jest.fn();
- const actual = shallow(blah );
+ const renderer = createRenderer();
+ renderer.render(blah );
// faux error
const err = new Error('err');
- const inst = actual.instance();
+ const inst = renderer.getMountedInstance() as Wrapper;
if (inst && inst.componentDidCatch) {
- inst.componentDidCatch(err, { componentStack: '' });
+ inst.componentDidCatch(err);
}
expect(onError).toHaveBeenCalledTimes(1);
diff --git a/src/client/rsg-components/Wrapper/Wrapper.ts b/src/client/rsg-components/Wrapper/Wrapper.ts
index 70c5b90ac..b95164520 100644
--- a/src/client/rsg-components/Wrapper/Wrapper.ts
+++ b/src/client/rsg-components/Wrapper/Wrapper.ts
@@ -1,7 +1,11 @@
import { Component } from 'react';
import PropTypes from 'prop-types';
-export default class Wrapper extends Component<{ onError: (e: Error) => void }> {
+interface Props {
+ onError: (e: Error) => void;
+ children?: React.ReactNode;
+}
+export default class Wrapper extends Component {
public static propTypes = {
children: PropTypes.node.isRequired,
onError: PropTypes.func.isRequired,
diff --git a/src/client/rsg-components/slots/IsolateButton.spec.tsx b/src/client/rsg-components/slots/IsolateButton.spec.tsx
index 28862a654..c530dd0c8 100644
--- a/src/client/rsg-components/slots/IsolateButton.spec.tsx
+++ b/src/client/rsg-components/slots/IsolateButton.spec.tsx
@@ -1,21 +1,24 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import IsolateButton from './IsolateButton';
it('should renderer a link to isolated mode', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should renderer a link to example isolated mode', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should renderer a link home in isolated mode', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
diff --git a/src/client/rsg-components/slots/UsageTabButton.spec.tsx b/src/client/rsg-components/slots/UsageTabButton.spec.tsx
index d4b0adac9..587dd40b1 100644
--- a/src/client/rsg-components/slots/UsageTabButton.spec.tsx
+++ b/src/client/rsg-components/slots/UsageTabButton.spec.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { createRenderer } from 'react-test-renderer/shallow';
import UsageTabButton from './UsageTabButton';
const props = {
@@ -8,13 +8,15 @@ const props = {
};
it('should renderer a button', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual).toMatchSnapshot();
+ expect(renderer.getRenderOutput()).toMatchSnapshot();
});
it('should renderer null if there are not props or methods', () => {
- const actual = shallow( );
+ const renderer = createRenderer();
+ renderer.render( );
- expect(actual.getElement()).toBeFalsy();
+ expect(renderer.getRenderOutput()).toBe(null);
});
diff --git a/src/client/utils/__tests__/compileCode.spec.ts b/src/client/utils/__tests__/compileCode.spec.ts
index 84c2f64a2..d6a19df9b 100644
--- a/src/client/utils/__tests__/compileCode.spec.ts
+++ b/src/client/utils/__tests__/compileCode.spec.ts
@@ -51,18 +51,16 @@ React.createElement( Button, null )"
`);
});
- test('wrap JSX in Fragment', () => {
- const result = compileCode(
- `
- Click
-
`,
- compilerConfig
+ test('wrap JSX in Fragment if adjacent on line 1', () => {
+ const result = compileCode(` `, compilerConfig);
+ expect(result).toMatchInlineSnapshot(
+ `"React.createElement( React.Fragment, null, React.createElement( 'span', null ), React.createElement( 'span', null ) );"`
);
- expect(result).toMatchInlineSnapshot(`
-"React.createElement( React.Fragment, null, React.createElement( 'div', null,
- React.createElement( 'button', null, \\"Click\\" )
-) );"
-`);
+ });
+
+ test('don’t wrap JSX in Fragment if there is only one statement', () => {
+ const result = compileCode(` ;`, compilerConfig);
+ expect(result).toMatchInlineSnapshot(`"React.createElement( Button, null );"`);
});
test('don’t wrap JSX in Fragment if it’s in the middle', () => {
diff --git a/src/client/utils/compileCode.ts b/src/client/utils/compileCode.ts
index 722f15e88..236f13f1e 100644
--- a/src/client/utils/compileCode.ts
+++ b/src/client/utils/compileCode.ts
@@ -18,11 +18,25 @@ export default function compileCode(
onError?: (err: Error) => void
): string {
try {
- const wrappedCode = startsWithJsx(code) ? wrapCodeInFragment(code) : code;
- const compiledCode = compile(wrappedCode, compilerConfig);
- return transpileImports(compiledCode);
+ let compiledCode;
+
+ try {
+ compiledCode = compile(code, compilerConfig);
+ } catch (err) {
+ if (
+ err instanceof SyntaxError &&
+ err.message.startsWith('Adjacent JSX elements must be wrapped in an enclosing tag')
+ ) {
+ const wrappedCode = startsWithJsx(code) ? wrapCodeInFragment(code) : code;
+ compiledCode = compile(wrappedCode, compilerConfig);
+ } else if (onError && err instanceof Error) {
+ onError(err);
+ }
+ }
+
+ return compiledCode ? transpileImports(compiledCode) : '';
} catch (err) {
- if (onError) {
+ if (onError && err instanceof Error) {
onError(err);
}
}
diff --git a/src/loaders/__tests__/examples-loader.spec.ts b/src/loaders/__tests__/examples-loader.spec.ts
index f1898903f..f37105110 100644
--- a/src/loaders/__tests__/examples-loader.spec.ts
+++ b/src/loaders/__tests__/examples-loader.spec.ts
@@ -1,4 +1,3 @@
-import { encode } from 'qss';
import examplesLoader from '../examples-loader';
/* eslint-disable no-new-func */
@@ -15,11 +14,8 @@ const subComponentQuery = {
shouldShowDefaultExample: false,
};
-
-const getQuery = (options = {}) => encode({ ...query, ...options }, '?');
-const getSubComponentQuery = (options = {}) => encode({ ...subComponentQuery, ...options }, '?');
-
-
+const getQueryOptions = (options = {}) => ({ ...query, ...options });
+const getSubComponentQueryOptions = (options = {}) => ({ ...subComponentQuery, ...options });
it('should return valid, parsable JS', () => {
const exampleMarkdown = `
@@ -35,7 +31,7 @@ text
`;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -58,33 +54,16 @@ it('should replace all occurrences of __COMPONENT__ with provided query.displayN
const result = examplesLoader.call(
{
- query: getQuery({ shouldShowDefaultExample: true }),
+ getOptions: () => getQueryOptions({ shouldShowDefaultExample: true }),
_styleguidist: {},
} as any,
exampleMarkdown
);
expect(result).not.toMatch(/__COMPONENT__/);
const componentHtml = result.match(/(.*?)<\/div>/);
- expect(componentHtml && componentHtml[0]).toMatchInlineSnapshot(`
-
- \\n\\t
-
- \\n\\t\\t
-
- text
-
- \\n\\t\\t
-
- Name of component: FooComponent
-
- \\n\\t
-
- \\n\\t
-
-
- \\n
-
- `);
+ expect(componentHtml && componentHtml[0]).toMatchInlineSnapshot(
+ `"
\\\\n\\\\t\\\\n\\\\t\\\\ttext \\\\n\\\\t\\\\tName of component: FooComponent \\\\n\\\\t \\\\n\\\\t \\\\n
"`
+ );
});
it('should pass updateExample function from config to chunkify', () => {
@@ -93,10 +72,10 @@ it('should pass updateExample function from config to chunkify', () => {
Hello world!
\`\`\`
`;
- const updateExample = jest.fn(props => props);
+ const updateExample = jest.fn((props) => props);
examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
resourcePath: '/path/to/foo/examples/file',
_styleguidist: {
updateExample,
@@ -127,7 +106,7 @@ Two:
`;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -148,7 +127,7 @@ One:
`;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -167,7 +146,7 @@ it('should work with multiple JSX element on the root level', () => {
`;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -181,7 +160,7 @@ it('should prepend example code with React require()', () => {
const exampleMarkdown = ` `;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -198,7 +177,7 @@ it('should prepend example code with component require()', () => {
const exampleMarkdown = ` `;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -215,7 +194,7 @@ it('should prepend example code with root component require() for sub components
const exampleMarkdown = ` `;
const result = examplesLoader.call(
{
- query: getSubComponentQuery(),
+ getOptions: () => getSubComponentQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -235,7 +214,7 @@ it('should allow explicit import of React and component module', () => {
`;
const result = examplesLoader.call(
{
- query: getQuery(),
+ getOptions: () => getQueryOptions(),
_styleguidist: {},
} as any,
exampleMarkdown
@@ -258,7 +237,7 @@ it('should works for any Markdown file, without a current component', () => {
`;
const result = examplesLoader.call(
{
- query: '',
+ getOptions: () => ({}),
_styleguidist: {},
} as any,
exampleMarkdown
diff --git a/src/loaders/examples-loader.ts b/src/loaders/examples-loader.ts
index 7b152c002..66299f249 100644
--- a/src/loaders/examples-loader.ts
+++ b/src/loaders/examples-loader.ts
@@ -3,7 +3,6 @@ import filter from 'lodash/filter';
import map from 'lodash/map';
import values from 'lodash/values';
import flatten from 'lodash/flatten';
-import loaderUtils from 'loader-utils';
import { generate } from 'escodegen';
import toAst from 'to-ast';
import { builders as b } from 'ast-types';
@@ -21,8 +20,7 @@ const EVAL_IN_CONTEXT_PATH = absolutize('utils/client/evalInContext');
export default function examplesLoader(this: Rsg.StyleguidistLoaderContext, source: string) {
const config = this._styleguidist;
- const { file, displayName, shouldShowDefaultExample, customLangs } =
- loaderUtils.getOptions(this) || {};
+ const { file, displayName, shouldShowDefaultExample, customLangs } = this.getOptions();
// Replace placeholders (__COMPONENT__) with the passed-in component name
if (shouldShowDefaultExample) {
@@ -80,7 +78,7 @@ export default function examplesLoader(this: Rsg.StyleguidistLoaderContext, sour
// Stringify examples object except the evalInContext function
const examplesWithEval: (Rsg.RuntimeCodeExample | Rsg.MarkdownExample)[] = examples.map(
- example => {
+ (example) => {
if (example.type === 'code') {
return { ...example, evalInContext: { toAST: () => b.identifier('evalInContext') } as any };
} else {
diff --git a/src/loaders/props-loader.ts b/src/loaders/props-loader.ts
index af7a9f3c1..5d1e7ecfa 100644
--- a/src/loaders/props-loader.ts
+++ b/src/loaders/props-loader.ts
@@ -16,13 +16,13 @@ const logger = createLogger('rsg');
const ERROR_MISSING_DEFINITION = 'No suitable component definition found.';
-export default function(this: Rsg.StyleguidistLoaderContext, source: string) {
+export default function (this: Rsg.StyleguidistLoaderContext, source: string) {
const file: string = this.request.split('!').pop() || '';
const config = this._styleguidist;
// Setup Webpack context dependencies to enable hot reload when adding new files or updating any of component dependencies
if (config.contextDependencies) {
- config.contextDependencies.forEach(dir => this.addContextDependency(dir));
+ config.contextDependencies.forEach((dir) => this.addContextDependency(dir));
}
const defaultParser = (
@@ -30,7 +30,7 @@ export default function(this: Rsg.StyleguidistLoaderContext, source: string) {
code: string,
resolver: (
ast: ASTNode,
- parser: { parse: (code: string) => ASTNode }
+ parser: { parse: (input: string) => ASTNode }
) => NodePath | NodePath[],
handlers: Handler[]
) => parse(code, resolver, handlers, { filename: filePath });
@@ -48,18 +48,20 @@ export default function(this: Rsg.StyleguidistLoaderContext, source: string) {
docs = docs[0];
}
} catch (err) {
- const errorMessage = err.toString();
- const componentPath = path.relative(process.cwd(), file);
- const message =
- errorMessage === `Error: ${ERROR_MISSING_DEFINITION}`
- ? `${componentPath} matches a pattern defined in “components” or “sections” options in your ` +
- 'style guide config but doesn’t export a component.\n\n' +
- 'It usually happens when using third-party libraries, see possible solutions here:\n' +
- `${consts.DOCS_THIRDPARTIES}`
- : `Cannot parse ${componentPath}: ${err}\n\n` +
- 'It usually means that react-docgen does not understand your source code, try to file an issue here:\n' +
- 'https://github.com/reactjs/react-docgen/issues';
- logger.warn(message);
+ if (err instanceof Error) {
+ const errorMessage = err.toString();
+ const componentPath = path.relative(process.cwd(), file);
+ const message =
+ errorMessage === `Error: ${ERROR_MISSING_DEFINITION}`
+ ? `${componentPath} matches a pattern defined in “components” or “sections” options in your ` +
+ 'style guide config but doesn’t export a component.\n\n' +
+ 'It usually happens when using third-party libraries, see possible solutions here:\n' +
+ `${consts.DOCS_THIRDPARTIES}`
+ : `Cannot parse ${componentPath}: ${err}\n\n` +
+ 'It usually means that react-docgen does not understand your source code, try to file an issue here:\n' +
+ 'https://github.com/reactjs/react-docgen/issues';
+ logger.warn(message);
+ }
}
const tempDocs = getProps(docs, file);
diff --git a/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap b/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap
index 5dd7ea958..c51b1bd33 100644
--- a/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap
+++ b/src/loaders/utils/__tests__/__snapshots__/chunkify.spec.ts.snap
@@ -7,9 +7,7 @@ Array [
"type": "markdown",
},
Object {
- "content":
- Example in vue
- ,
+ "content": "Example in vue ",
"settings": Object {},
"type": "code",
},
@@ -23,18 +21,14 @@ Array [
"type": "markdown",
},
Object {
- "content":
- Hello Markdown!
- ,
+ "content": "Hello Markdown! ",
"settings": Object {
"showcode": true,
},
"type": "code",
},
Object {
- "content":
- Example in frame and Without editor
- ,
+ "content": "Example in frame and Without editor ",
"settings": Object {
"frame": Object {
"width": "400px",
@@ -47,9 +41,7 @@ Array [
"type": "markdown",
},
Object {
- "content":
- Hello Markdown!
- ,
+ "content": "Hello Markdown! ",
"settings": Object {
"noeditor": true,
},
@@ -71,9 +63,7 @@ Array [
"type": "markdown",
},
Object {
- "content":
- Example in jsx with undefined extensions
- ,
+ "content": "Example in jsx with undefined extensions ",
"settings": Object {},
"type": "code",
},
@@ -101,9 +91,7 @@ This code example should be rendered as a playground:",
"type": "markdown",
},
Object {
- "content":
- Hello Markdown!
- ,
+ "content": "Hello Markdown! ",
"settings": Object {},
"type": "code",
},
@@ -112,9 +100,7 @@ This code example should be rendered as a playground:",
"type": "markdown",
},
Object {
- "content":
- Hello Markdown!
- ,
+ "content": "Hello Markdown! ",
"settings": Object {},
"type": "code",
},
@@ -123,9 +109,7 @@ This code example should be rendered as a playground:",
"type": "markdown",
},
Object {
- "content":
- Hello Markdown!
- ,
+ "content": "Hello Markdown! ",
"settings": Object {
"noeditor": true,
},
diff --git a/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap b/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap
index 00a6fd6a1..ed824d848 100644
--- a/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap
+++ b/src/loaders/utils/__tests__/__snapshots__/highlightCode.spec.ts.snap
@@ -1,27 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should highlight code with specified language 1`] = `
-
-
-
- <
-
- p
-
-
- >
-
-
-Hello React
-
-
-
- </
-
- p
-
-
- >
-
-
-`;
+exports[`should highlight code with specified language 1`] = `"< p> Hello React</ p> "`;
diff --git a/src/loaders/utils/getAst.ts b/src/loaders/utils/getAst.ts
index 8a07985c4..e21b542c9 100644
--- a/src/loaders/utils/getAst.ts
+++ b/src/loaders/utils/getAst.ts
@@ -20,7 +20,10 @@ export default function getAst(
try {
return parser.parse(code, ACORN_OPTIONS);
} catch (err) {
- logger.debug(`Acorn cannot parse example code: ${err.message}\n\nCode:\n${code}`);
+ if (err instanceof Error) {
+ logger.debug(`Acorn cannot parse example code: ${err.message}\n\nCode:\n${code}`);
+ return undefined;
+ }
return undefined;
}
}
diff --git a/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap b/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
index ea750bfa6..3ca3b9057 100644
--- a/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
+++ b/src/scripts/__tests__/__snapshots__/make-webpack-config.spec.ts.snap
@@ -28,7 +28,6 @@ Array [
"a/b.js",
"c/d.css",
"~/src/client/index",
- "~/node_modules/react-dev-utils/webpackHotDevClient.js",
]
`;
diff --git a/src/scripts/__tests__/config.spec.ts b/src/scripts/__tests__/config.spec.ts
index 449332530..2d703efb5 100644
--- a/src/scripts/__tests__/config.spec.ts
+++ b/src/scripts/__tests__/config.spec.ts
@@ -50,7 +50,9 @@ it('should throw if config has errors', () => {
components: 42,
} as any);
} catch (err) {
- expect(err.message).toMatch('should be string, function, or array');
+ if (err instanceof Error) {
+ expect(err.message).toMatch('should be string, function, or array');
+ }
}
});
@@ -167,7 +169,9 @@ it('should throw if defaultExample does not exist', () => {
defaultExample: 'pizza',
});
} catch (err) {
- expect(err.message).toMatch('does not exist');
+ if (err instanceof Error) {
+ expect(err.message).toMatch('does not exist');
+ }
}
});
@@ -233,7 +237,9 @@ it('should throw when old template as a string option passed', () => {
template: 'pizza',
});
} catch (err) {
- expect(err.message).toMatch('format has been changed');
+ if (err instanceof Error) {
+ expect(err.message).toMatch('format has been changed');
+ }
}
});
@@ -244,7 +250,9 @@ it('should throw when editorConfig option passed', () => {
editorConfig: { theme: 'foo' },
});
} catch (err) {
- expect(err.message).toMatch('config option was removed');
+ if (err instanceof Error) {
+ expect(err.message).toMatch('config option was removed');
+ }
}
});
diff --git a/src/scripts/__tests__/create-server.spec.ts b/src/scripts/__tests__/create-server.spec.ts
index af0b01031..4b1c91494 100644
--- a/src/scripts/__tests__/create-server.spec.ts
+++ b/src/scripts/__tests__/create-server.spec.ts
@@ -1,4 +1,4 @@
-import { Output } from 'webpack';
+import { WebpackOptionsNormalized } from 'webpack';
import createServer from '../create-server';
import getConfig from '../config';
@@ -15,13 +15,24 @@ test('createServer should return an object containing a server instance', () =>
expect(result.app).toBeTruthy();
});
-test('createServer should return an object containing a production Webpack compiler', done => {
+test('createServer should support an array-valued assetsDir', (done) => {
+ process.chdir('test/apps/basic');
+ const config = getConfig({
+ assetsDir: ['src/components', 'src/components2'],
+ });
+ const result = createServer(config, 'production');
+ expect(result).toBeTruthy();
+ expect(result.app).toBeTruthy();
+ done();
+});
+
+test('createServer should return an object containing a production Webpack compiler', (done) => {
process.chdir('test/apps/basic');
const config = getConfig();
const result = createServer(config, 'production');
expect(result).toBeTruthy();
expect(result.compiler).toBeTruthy();
- let output: Output;
+ let output: WebpackOptionsNormalized['output'];
if (result.compiler && result.compiler.options && result.compiler.options.output) {
output = result.compiler.options.output;
} else {
@@ -33,13 +44,13 @@ test('createServer should return an object containing a production Webpack compi
done();
});
-test('createServer should return an object containing a development Webpack compiler', done => {
+test('createServer should return an object containing a development Webpack compiler', (done) => {
process.chdir('test/apps/basic');
const config = getConfig();
const result = createServer(config, 'development');
expect(result).toBeTruthy();
expect(result.compiler).toBeTruthy();
- let output: Output;
+ let output: WebpackOptionsNormalized['output'];
if (result.compiler && result.compiler.options && result.compiler.options.output) {
output = result.compiler.options.output;
} else {
@@ -50,3 +61,47 @@ test('createServer should return an object containing a development Webpack comp
expect(output.chunkFilename).toBe('build/[name].js');
done();
});
+
+test('createServer should apply some base config options', () => {
+ process.chdir('test/apps/basic');
+ const config = {
+ ...getConfig(),
+ serverHost: 'localhost',
+ serverPort: 6000,
+ };
+ const result = createServer(config, 'development');
+ expect(result).toBeTruthy();
+ expect(result.compiler).toBeTruthy();
+ expect(result.compiler.options.devServer).toMatchObject({
+ host: 'localhost',
+ port: 6000,
+ compress: true,
+ hot: true,
+ client: { logging: 'none' },
+ webSocketServer: 'ws',
+ });
+});
+
+test('createServer should allow overriding default devServer options', () => {
+ process.chdir('test/apps/basic');
+ const config = {
+ ...getConfig(),
+ webpackConfig: {
+ devServer: {
+ client: {
+ overlay: false,
+ progress: true,
+ },
+ },
+ },
+ };
+ const result = createServer(config, 'development');
+ expect(result).toBeTruthy();
+ expect(result.compiler).toBeTruthy();
+ expect(result.compiler.options.devServer).toMatchObject({
+ client: {
+ overlay: false,
+ progress: true,
+ },
+ });
+});
diff --git a/src/scripts/__tests__/make-webpack-config.spec.ts b/src/scripts/__tests__/make-webpack-config.spec.ts
index 5df8b375c..e744d0248 100644
--- a/src/scripts/__tests__/make-webpack-config.spec.ts
+++ b/src/scripts/__tests__/make-webpack-config.spec.ts
@@ -1,29 +1,34 @@
-import webpack, { Configuration } from 'webpack';
-import { Tapable } from 'tapable';
+import webpack, {
+ Compiler,
+ Configuration,
+ validate,
+ ValidationError,
+ WebpackPluginInstance,
+} from 'webpack';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import makeWebpackConfig from '../make-webpack-config';
import * as Rsg from '../../typings';
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const { validate } = require('webpack');
-
jest.mock('copy-webpack-plugin');
+type WebpackPlugin = WebpackPluginInstance | ((this: Compiler, compiler: Compiler) => void) | '...';
+
const styleguideConfig = ({
styleguideDir: __dirname,
require: [],
title: 'Style Guide',
} as unknown) as Rsg.SanitizedStyleguidistConfig;
-const getClasses = (plugins: Tapable.Plugin[] = [], name: string): Tapable.Plugin[] =>
- plugins.filter(x => x.constructor.name === name);
-const getClassNames = (plugins: Tapable.Plugin[] = []): string[] =>
- plugins.map(x => x.constructor.name);
+const getClasses = (plugins: WebpackPlugin[] = [], name: string): WebpackPlugin[] =>
+ plugins.filter((x) => x.constructor.name === name);
+
+const getClassNames = (plugins: WebpackPlugin[] = []): string[] =>
+ plugins.map((x) => x.constructor.name);
const process$env$nodeEnv = process.env.NODE_ENV;
beforeEach(() => {
- (CopyWebpackPlugin as jest.Mock).mockClear();
+ ((CopyWebpackPlugin as unknown) as jest.Mock).mockClear();
});
afterEach(() => {
@@ -34,12 +39,7 @@ it('should return a development config', () => {
const env = 'development';
const config = makeWebpackConfig(styleguideConfig, env);
- const errors = validate(config);
- expect(errors).toHaveLength(0);
-
- const plugins = getClassNames(config.plugins || []);
- expect(plugins).toContain('HotModuleReplacementPlugin');
-
+ expect(() => validate(config)).not.toThrow(ValidationError);
expect(config).toMatchObject({
mode: env,
});
@@ -49,8 +49,7 @@ it('should return a development config', () => {
it('should return a production config', () => {
const env = 'production';
const config = makeWebpackConfig(styleguideConfig, env);
- const errors = validate(config);
- expect(errors).toHaveLength(0);
+ expect(() => validate(config)).not.toThrow(ValidationError);
const plugins = getClassNames(config.plugins);
expect(plugins).toContain('CleanWebpackPlugin');
@@ -66,9 +65,8 @@ it('should return a production config', () => {
expect(config).toMatchObject({
mode: env,
});
- expect(
- getClasses(config.optimization && config.optimization.minimizer, 'TerserPlugin')
- ).toHaveLength(1);
+ const result = getClasses(config.optimization && config.optimization.minimizer, 'TerserPlugin');
+ expect(result).toHaveLength(1);
});
it('should set aliases', () => {
diff --git a/src/scripts/__tests__/server.spec.ts b/src/scripts/__tests__/server.spec.ts
index 57b579291..ff0206caa 100644
--- a/src/scripts/__tests__/server.spec.ts
+++ b/src/scripts/__tests__/server.spec.ts
@@ -4,7 +4,7 @@ import getConfig from '../config';
jest.mock('../create-server', () => () => {
return {
app: {
- listen: (port: number, host: string, cb: () => void) => cb(),
+ startCallback: (cb: () => void) => cb(),
close: (cb: () => void) => cb(),
},
compiler: {},
@@ -19,6 +19,6 @@ test('server should return an object containing a server instance', () => {
expect(callback).toBeCalled();
expect(serverInfo.app).toBeTruthy();
expect(serverInfo.compiler).toBeTruthy();
- expect(typeof serverInfo.app.listen).toBe('function');
+ expect(typeof serverInfo.app.startCallback).toBe('function');
expect(typeof serverInfo.app.close).toBe('function');
});
diff --git a/src/scripts/build.ts b/src/scripts/build.ts
index 0ad0280f4..518b19fbf 100644
--- a/src/scripts/build.ts
+++ b/src/scripts/build.ts
@@ -8,6 +8,6 @@ export default function build(
) {
return webpack(makeWebpackConfig(config, 'production'), (err, stats) => {
// require('fs').writeFileSync('stats.json', JSON.stringify(stats.toJson()));
- callback(err, stats);
+ callback(err as Error, stats as webpack.Stats);
});
}
diff --git a/src/scripts/create-server.ts b/src/scripts/create-server.ts
index e38ac3232..3b4272c0f 100644
--- a/src/scripts/create-server.ts
+++ b/src/scripts/create-server.ts
@@ -1,6 +1,5 @@
-import webpack, { Configuration } from 'webpack';
-import WebpackDevServer from 'webpack-dev-server';
-import merge from 'webpack-merge';
+import webpack from 'webpack';
+import WebpackDevServer, { Configuration } from 'webpack-dev-server';
import makeWebpackConfig from './make-webpack-config';
import * as Rsg from '../typings';
@@ -9,27 +8,39 @@ export default function createServer(
env: 'development' | 'production' | 'none'
): { app: WebpackDevServer; compiler: webpack.Compiler } {
const webpackConfig = makeWebpackConfig(config, env);
- const webpackDevServerConfig = merge(
- {
- noInfo: true,
- compress: true,
- clientLogLevel: 'none',
- hot: true,
- quiet: true,
- watchOptions: {
- ignored: /node_modules/,
- },
- watchContentBase: config.assetsDir !== undefined,
+
+ const baseConfig: Partial = {
+ host: config.serverHost,
+ port: config.serverPort,
+ compress: true,
+ hot: true,
+ client: {
+ logging: 'none',
+ },
+ static: Array.isArray(config.assetsDir)
+ ? config.assetsDir.map((assetsDir) => ({
+ directory: assetsDir,
+ watch: true,
+ publicPath: '/',
+ }))
+ : {
+ directory: config.assetsDir,
+ watch: true,
+ publicPath: '/',
+ },
+ devMiddleware: {
stats: webpackConfig.stats || {},
- } as Configuration,
- webpackConfig.devServer as Configuration,
- {
- contentBase: config.assetsDir,
- } as Configuration
- );
+ },
+ };
+
+ // Allow custom devServer options to override base config.
+ webpackConfig.devServer = {
+ ...baseConfig,
+ ...webpackConfig.devServer,
+ };
const compiler = webpack(webpackConfig);
- const devServer = new WebpackDevServer(compiler, webpackDevServerConfig);
+ const devServer = new WebpackDevServer(webpackConfig.devServer, compiler);
// User defined customizations
if (config.configureServer) {
diff --git a/src/scripts/index.esm.ts b/src/scripts/index.esm.ts
index 8dac92805..1da928914 100644
--- a/src/scripts/index.esm.ts
+++ b/src/scripts/index.esm.ts
@@ -15,8 +15,8 @@ import * as Rsg from '../typings';
* @param {object} [config] Styleguidist config.
* @returns {object} API.
*/
-export default function(configArg?: Rsg.StyleguidistConfig | string) {
- const config = getConfig(configArg, conf => {
+export default function (configArg?: Rsg.StyleguidistConfig | string) {
+ const config = getConfig(configArg, (conf) => {
setupLogger(conf.logger as Record void>, conf.verbose, {});
return conf;
});
@@ -29,7 +29,11 @@ export default function(configArg?: Rsg.StyleguidistConfig | string) {
* @return {Compiler} Webpack Compiler instance.
*/
build(
- callback: (err: Error, config: Rsg.SanitizedStyleguidistConfig, stats: webpack.Stats) => void
+ callback: (
+ err: Error,
+ styleguidistConfig: Rsg.SanitizedStyleguidistConfig,
+ stats: webpack.Stats
+ ) => void
) {
return build(config, (err: Error, stats: webpack.Stats) => callback(err, config, stats));
},
@@ -41,8 +45,13 @@ export default function(configArg?: Rsg.StyleguidistConfig | string) {
* @return {ServerInfo.App} Webpack-Dev-Server.
* @return {ServerInfo.Compiler} Webpack Compiler instance.
*/
- server(callback: (err: Error | undefined, config: Rsg.SanitizedStyleguidistConfig) => void) {
- return server(config, err => callback(err, config));
+ server(
+ callback: (
+ err: Error | undefined,
+ styleguidistConfig: Rsg.SanitizedStyleguidistConfig
+ ) => void
+ ) {
+ return server(config, (err) => callback(err, config));
},
/**
diff --git a/src/scripts/make-webpack-config.ts b/src/scripts/make-webpack-config.ts
index d4da2d169..9665df9a3 100644
--- a/src/scripts/make-webpack-config.ts
+++ b/src/scripts/make-webpack-config.ts
@@ -1,6 +1,6 @@
import path from 'path';
import castArray from 'lodash/castArray';
-import webpack, { Configuration, Resolve } from 'webpack';
+import webpack, { Configuration, Resolver } from 'webpack';
import TerserPlugin from 'terser-webpack-plugin';
import { MiniHtmlWebpackPlugin } from 'mini-html-webpack-plugin';
import MiniHtmlWebpackTemplate from '@vxna/mini-html-webpack-template';
@@ -19,7 +19,7 @@ const RENDERER_REGEXP = /Renderer$/;
const sourceDir = path.resolve(__dirname, '../client');
interface AliasedConfiguration extends Configuration {
- resolve: Resolve & { alias: Record };
+ resolve: Resolver['resolve'] & { alias: Record };
}
export default function (
@@ -99,7 +99,7 @@ export default function (
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [`${config.styleguideDir}/build/**/*`],
verbose: config.verbose === true,
- } as any),
+ }),
],
optimization: {
minimize: config.minimize === true,
@@ -119,10 +119,8 @@ export default function (
}
} else {
webpackConfig = merge(webpackConfig, {
- entry: [require.resolve('react-dev-utils/webpackHotDevClient')],
- plugins: [new webpack.HotModuleReplacementPlugin()],
devServer: {
- transportMode: 'ws',
+ webSocketServer: 'ws',
},
});
}
diff --git a/src/scripts/server.ts b/src/scripts/server.ts
index 0fa54308a..a3e714f7b 100644
--- a/src/scripts/server.ts
+++ b/src/scripts/server.ts
@@ -10,7 +10,7 @@ export default function server(
const env = 'development';
const serverInfo = createServer(config, env);
- serverInfo.app.listen(config.serverPort, config.serverHost, callback);
+ serverInfo.app.startCallback(callback);
return serverInfo;
}
diff --git a/src/scripts/utils/StyleguidistOptionsPlugin.ts b/src/scripts/utils/StyleguidistOptionsPlugin.ts
index f7cf529bb..2dcb53695 100644
--- a/src/scripts/utils/StyleguidistOptionsPlugin.ts
+++ b/src/scripts/utils/StyleguidistOptionsPlugin.ts
@@ -1,4 +1,4 @@
-import webpack, { compilation, Compiler, WebpackPluginInstance, loader } from 'webpack';
+import webpack, { Compilation, Compiler, WebpackPluginInstance, LoaderContext } from 'webpack';
import * as Rsg from '../../typings';
@@ -15,7 +15,10 @@ export default class StyleguidistOptionsPlugin implements WebpackPluginInstance
this.options = options;
}
- private pluginFunc = (context: Rsg.StyleguidistLoaderContext, module: loader.LoaderContext) => {
+ private pluginFunc = (
+ context: Rsg.StyleguidistLoaderContext,
+ module: LoaderContext
+ ) => {
if (!module.resource) {
return;
}
@@ -24,21 +27,10 @@ export default class StyleguidistOptionsPlugin implements WebpackPluginInstance
/**
*
- * @param compil webpack 4 `compilation.Compilation`, webpack 5 `Compilation`
+ * @param compil Compilation
*/
- private plugin = (compil: any) => {
- // Webpack 5
- /* istanbul ignore next */
- if ('NormalModule' in webpack) {
- // @ts-ignore
- webpack.NormalModule.getCompilationHooks(compil).loader.tap(
- 'StyleguidistOptionsPlugin',
- this.pluginFunc as any
- );
- return;
- }
- // Webpack 4
- (compil as compilation.Compilation).hooks.normalModuleLoader.tap(
+ private plugin = (compil: Compilation) => {
+ webpack.NormalModule.getCompilationHooks(compil).loader.tap(
'StyleguidistOptionsPlugin',
this.pluginFunc as any
);
diff --git a/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts b/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
index 7e3dd7a7c..8629e9e25 100644
--- a/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
+++ b/src/scripts/utils/__tests__/StyleguidistOptionsPlugin.spec.ts
@@ -4,58 +4,54 @@ import * as Rsg from '../../../typings';
const options: any = {
foo: 42,
};
+const mockContext: { _styleguidist?: Rsg.StyleguidistConfig } = {};
-it('should attach Styleguidist config when webpack 4 is used', () => {
- const context: { _styleguidist?: Rsg.StyleguidistConfig } = {};
+let mockedModule: Record;
+
+jest.mock('webpack', () => {
+ return {
+ NormalModule: {
+ getCompilationHooks: () => {
+ return {
+ loader: {
+ tap: (moduleName: string, compilationCallback: (context: any, opt: any) => void) => {
+ compilationCallback(mockContext, mockedModule);
+ },
+ },
+ };
+ },
+ },
+ };
+});
+
+it('should do nothing when module.resource is not present', () => {
+ mockedModule = {};
const compiler = {
hooks: {
compilation: {
tap: (name: string, callback: (opt: any) => void) => {
- callback({
- hooks: {
- normalModuleLoader: {
- tap: (
- moduleName: string,
- compilationCallback: (context: any, opt: any) => void
- ) => {
- compilationCallback(context, { resource: 'pizza' });
- },
- },
- },
- });
+ callback({});
},
},
},
};
const plugin = new StyleguidistOptionsPlugin(options);
plugin.apply(compiler as any);
- expect(context._styleguidist).toEqual(options);
+ expect(mockContext._styleguidist).toBeFalsy();
});
-it('should do nothing when resource is empty', () => {
- const context: { _styleguidist?: Rsg.StyleguidistConfig } = {};
+it('should attach Styleguidist config options', () => {
+ mockedModule = { resource: 'test' };
const compiler = {
hooks: {
compilation: {
tap: (name: string, callback: (opt: any) => void) => {
- callback({
- hooks: {
- normalModuleLoader: {
- tap: (
- moduleName: string,
- compilationCallback: (context: any, opt: any) => void
- ) => {
- compilationCallback(context, {});
- },
- },
- },
- });
+ callback({});
},
},
},
};
const plugin = new StyleguidistOptionsPlugin(options);
plugin.apply(compiler as any);
-
- expect(context._styleguidist).toBeFalsy();
+ expect(mockContext._styleguidist).toEqual(options);
});
diff --git a/src/scripts/utils/mergeWebpackConfig.ts b/src/scripts/utils/mergeWebpackConfig.ts
index 9998b9945..ff2e9b239 100644
--- a/src/scripts/utils/mergeWebpackConfig.ts
+++ b/src/scripts/utils/mergeWebpackConfig.ts
@@ -1,8 +1,7 @@
import mergeBase from 'webpack-merge';
import isFunction from 'lodash/isFunction';
import omit from 'lodash/omit';
-import { Configuration } from 'webpack';
-import { Tapable } from 'tapable';
+import { Configuration, WebpackPluginInstance } from 'webpack';
const IGNORE_SECTIONS = ['entry', 'externals', 'output', 'watch', 'stats', 'styleguidist'];
const IGNORE_SECTIONS_ENV: Record = {
@@ -28,7 +27,7 @@ const merge = mergeBase({
customizeArray: mergeBase.unique(
'plugins',
IGNORE_PLUGINS,
- (plugin: Tapable.Plugin) => plugin.constructor && plugin.constructor.name
+ (plugin: WebpackPluginInstance) => plugin.constructor && plugin.constructor.name
),
});
diff --git a/src/scripts/utils/sanitizeConfig.ts b/src/scripts/utils/sanitizeConfig.ts
index 2ee690de6..fbadbbe41 100644
--- a/src/scripts/utils/sanitizeConfig.ts
+++ b/src/scripts/utils/sanitizeConfig.ts
@@ -1,6 +1,5 @@
import fs from 'fs';
import path from 'path';
-import * as isDirectory from 'is-directory';
import castArray from 'lodash/castArray';
import isBoolean from 'lodash/isBoolean';
import isFunction from 'lodash/isFunction';
@@ -33,9 +32,20 @@ const typeCheckers: Record boolean> = {
};
const typesList = (types: string[]) => listify(types, { finalWord: 'or' });
-const shouldBeFile = (types: string[]) => types.some(type => type.includes('file'));
-const shouldBeDirectory = (types: string[]) => types.some(type => type.includes('directory'));
-const shouldExist = (types: string[]) => types.some(type => type.includes('existing'));
+const shouldBeFile = (types: string[]) => types.some((type) => type.includes('file'));
+const shouldBeDirectory = (types: string[]) => types.some((type) => type.includes('directory'));
+const shouldExist = (types: string[]) => types.some((type) => type.includes('existing'));
+
+function isDirectory(pathString: string): boolean {
+ try {
+ return fs.lstatSync(pathString).isDirectory();
+ } catch (e: any) {
+ if (e.code !== 'ENOENT') {
+ throw e;
+ }
+ return false;
+ }
+}
/**
* Validates and normalizes config.
@@ -106,7 +116,7 @@ export default function sanitizeConfig>(
const types = castArray(props.type);
// Check type
- const hasRightType = types.some(type => {
+ const hasRightType = types.some((type) => {
if (!typeCheckers[type]) {
throw new StyleguidistError(
`Wrong type ${kleur.bold(type)} specified for ${kleur.bold(key)} in schema.`
@@ -146,7 +156,7 @@ ${stringify(example)}`
key
);
}
- if (shouldBeDirectory(types) && !isDirectory.sync(value)) {
+ if (shouldBeDirectory(types) && !isDirectory(value)) {
throw new StyleguidistError(
`A directory specified in ${kleur.bold(key)} config option does not exist:\n${value}`,
key
diff --git a/src/typings/RsgStyleguidistConfig.ts b/src/typings/RsgStyleguidistConfig.ts
index 73428c34d..dc71c866d 100644
--- a/src/typings/RsgStyleguidistConfig.ts
+++ b/src/typings/RsgStyleguidistConfig.ts
@@ -1,5 +1,5 @@
import WebpackDevServer from 'webpack-dev-server';
-import { Configuration, loader } from 'webpack';
+import { Configuration, LoaderContext } from 'webpack';
import { TransformOptions } from 'buble';
import { Handler, DocumentationObject, PropDescriptor } from 'react-docgen';
import { ASTNode } from 'ast-types';
@@ -12,7 +12,13 @@ import { CodeExample } from './RsgExample';
import { ConfigSection, Section } from './RsgSection';
import { Theme } from './RsgTheme';
-export interface StyleguidistLoaderContext extends loader.LoaderContext {
+type OptionsType = {
+ displayName: string;
+ file: string;
+ shouldShowDefaultExample: string;
+ customLangs: string[];
+};
+export interface StyleguidistLoaderContext extends LoaderContext {
_styleguidist: SanitizedStyleguidistConfig;
}
@@ -52,7 +58,7 @@ interface BaseStyleguidistConfig {
code: string,
resolver: (
ast: ASTNode,
- parser: { parse: (code: string) => ASTNode }
+ parser: { parse: (input: string) => ASTNode }
) => NodePath | NodePath[],
handlers: Handler[]
): DocumentationObject;
diff --git a/src/typings/dependencies/is-directory.ts b/src/typings/dependencies/is-directory.ts
deleted file mode 100644
index 0bddec91a..000000000
--- a/src/typings/dependencies/is-directory.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-declare module 'is-directory' {
- const isDirectory: {
- (pathToCheck: string, callback: (isDir: boolean) => void): void;
- sync(pathToCheck: string): boolean;
- };
- export = isDirectory;
-}
diff --git a/src/typings/dependencies/webpack-merge.ts b/src/typings/dependencies/webpack-merge.ts
index 2edff39bf..8fb123421 100644
--- a/src/typings/dependencies/webpack-merge.ts
+++ b/src/typings/dependencies/webpack-merge.ts
@@ -1,6 +1,5 @@
declare module 'webpack-merge' {
- import { Tapable } from 'tapable';
- import { Configuration } from 'webpack';
+ import { Configuration, WebpackPluginInstance } from 'webpack';
type MetaConfig = Configuration | ((env?: string) => Configuration);
type mergeFunction = (...configs: MetaConfig[]) => Configuration;
@@ -14,7 +13,7 @@ declare module 'webpack-merge' {
unique(
key: string,
uniques: string[],
- getter?: (plugin: Tapable.Plugin) => string | undefined | false
+ getter?: (plugin: WebpackPluginInstance) => string | undefined | false
): customizeArrayFuntion;
};
export = webpackMerge;
diff --git a/src/typings/index.ts b/src/typings/index.ts
index 62dd6c895..2eabffc64 100644
--- a/src/typings/index.ts
+++ b/src/typings/index.ts
@@ -11,7 +11,6 @@ import './dependencies/glogg';
import './dependencies/mini-html-webpack-template';
import './dependencies/stripHtmlComments';
import './dependencies/deepfreeze';
-import './dependencies/is-directory';
import './dependencies/q-i';
import './dependencies/to-ast';
diff --git a/test/apps/basic/package.json b/test/apps/basic/package.json
index 97a04d306..03dabf9cf 100644
--- a/test/apps/basic/package.json
+++ b/test/apps/basic/package.json
@@ -1,8 +1,6 @@
{
"name": "pizza-basic",
"devDependencies": {
- "file-loader": "~0.9.0",
- "postcss-loader": "~0.13.0",
- "url-loader": "~0.5.7"
+ "postcss-loader": "^7.0.0"
}
}
diff --git a/test/apps/cra/package.json b/test/apps/cra/package.json
index e24dec4e0..a56c1ca73 100644
--- a/test/apps/cra/package.json
+++ b/test/apps/cra/package.json
@@ -1,9 +1,7 @@
{
"name": "pizza-cra",
"devDependencies": {
- "file-loader": "~0.9.0",
- "postcss-loader": "~0.13.0",
- "react-scripts": "~0.6.1",
- "url-loader": "~0.5.7"
+ "postcss-loader": "^7.0.0",
+ "react-scripts": "^5.0.0"
}
}
diff --git a/test/deabsdeepSerializer.js b/test/deabsdeepSerializer.js
new file mode 100644
index 000000000..2b7e07420
--- /dev/null
+++ b/test/deabsdeepSerializer.js
@@ -0,0 +1,92 @@
+const escape = require('escape-string-regexp');
+const isObject = require('is-plain-obj');
+const path = require('path');
+
+const MASK = '~';
+const DIRNAME = getRootDir();
+
+/**
+ * Recursively replace absolute paths in object keys and values or in array values with a “~”.
+ *
+ * @param {object} obj
+ * @param {object} [options]
+ * @param {string} [options.root]
+ * @param {string} [options.mask]
+ * @return {object}
+ */
+function deabsDeep(obj, options) {
+ options = options || {};
+ const root = options.root || DIRNAME;
+ const mask = options.mask || MASK;
+
+ const regExp = new RegExp(escape(root), 'g');
+ const deabs = (s) => (typeof s === 'string' ? s.replace(regExp, mask) : s);
+
+ if (Array.isArray(obj)) {
+ return obj.map(deabs);
+ }
+
+ return mapObj(obj, (key, value) => [deabs(key), deabs(value)]);
+}
+
+function getRootDir(dir) {
+ dir = dir || __dirname;
+ const m = dir.match(/[\\/]node_modules[\\/]/);
+ return m ? dir.substring(0, m.index) : path.resolve(__dirname, '..');
+}
+
+/* istanbul ignore next */
+function mapObj(obj, fn, seen) {
+ seen = seen || new WeakMap();
+ if (seen.has(obj)) {
+ return seen.get(obj);
+ }
+
+ const target = {};
+
+ seen.set(obj, target);
+
+ for (const key of Object.keys(obj)) {
+ const val = obj[key];
+ const res = fn(key, val, obj);
+ let newVal = res[1];
+
+ if (isObject(newVal)) {
+ if (Array.isArray(newVal)) {
+ newVal = newVal.map((x) => (isObject(x) ? mapObj(x, fn, seen) : x));
+ } else {
+ newVal = mapObj(newVal, fn, seen);
+ }
+ }
+
+ target[res[0]] = newVal;
+ }
+
+ // The $$typeof property is a React marker that's used for serialization.
+ // deabsdeep doesn't know about React Elements but since it's registered globally,
+ // it should keep this property on the object.
+ if (obj.$$typeof) {
+ target.$$typeof = obj.$$typeof;
+ }
+
+ return target;
+}
+
+// Borrowed from https://github.com/eyolas/jest-serializer-supertest
+const KEY = '__JEST_SERIALIZER_DEABSDEEP__';
+
+module.exports = {
+ test(val) {
+ return (Array.isArray(val) || isObject(val)) && !Object.prototype.hasOwnProperty.call(val, KEY);
+ },
+ print(val, serialize) {
+ const newVal = deabsDeep(val);
+
+ // To skip maximum call stack size exceeded
+ Object.defineProperty(newVal, KEY, {
+ enumerable: false,
+ });
+
+ return serialize(newVal);
+ },
+};
diff --git a/test/jestsetup.js b/test/jestsetup.js
index 06a42072b..b1b8bb3c7 100644
--- a/test/jestsetup.js
+++ b/test/jestsetup.js
@@ -1,17 +1,8 @@
/* eslint-disable no-console */
-import { configure, shallow, render, mount } from 'enzyme';
import keymirror from 'keymirror';
-import Adapter from 'enzyme-adapter-react-16';
import * as theme from '../src/client/styles/theme';
-configure({ adapter: new Adapter() });
-
-// Make Enzyme functions available in all test files without importing
-global.shallow = shallow;
-global.render = render;
-global.mount = mount;
-
// Get class names from styles function
global.classes = (styles) => keymirror(styles(theme));
diff --git a/tsconfig.json b/tsconfig.json
index 889587595..8bf791eaa 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,7 @@
"allowSyntheticDefaultImports": true,
"outDir": "./lib",
"lib": ["dom"],
+ "skipLibCheck": true,
"paths": {
"rsg-components/*": ["src/client/rsg-components/*"]
}