Skip to content

Commit 470461e

Browse files
committed
Setup tests for different utility functions and components
1 parent a5b4100 commit 470461e

23 files changed

+1301
-28
lines changed

jest-setup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import '@testing-library/jest-dom';

jest.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const config: Config = {
9090
// ],
9191

9292
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
93-
// moduleNameMapper: {},
93+
moduleNameMapper: { 'lodash-es': 'lodash' },
9494

9595
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
9696
// modulePathIgnorePatterns: [],
@@ -137,7 +137,7 @@ const config: Config = {
137137
// setupFiles: [],
138138

139139
// A list of paths to modules that run some code to configure or set up the testing framework before each test
140-
// setupFilesAfterEnv: [],
140+
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
141141

142142
// The number of seconds after which a test is considered as slow and reported as such in the results.
143143
// slowTestThreshold: 5,

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@types/jest": "^29.5.14",
2424
"@types/node": "^22.10.2",
2525
"@types/rollup-plugin-peer-deps-external": "^2.2.5",
26+
"@types/testing-library__jest-dom": "^5.14.9",
2627
"babel-jest": "^29.7.0",
2728
"eslint": "^9.13.0",
2829
"eslint-config-prettier": "^9.1.0",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`PluginBox renders ErrorBox when editSchema is null 1`] = `
4+
<div
5+
class="css-133ygyc"
6+
data-testid="plugin-box-error"
7+
>
8+
Plugin
9+
<code
10+
class="chakra-code css-1fu8sp0"
11+
>
12+
TestPlugin
13+
</code>
14+
has no edit schema.
15+
</div>
16+
`;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import { Formik } from 'formik';
4+
import { ChakraProvider } from '@chakra-ui/react';
5+
6+
import { PluginBox } from './plugin-box';
7+
8+
const mockPlugin = {
9+
name: 'TestPlugin',
10+
editSchema: jest.fn()
11+
};
12+
13+
// Custom renderer to add context or providers if needed
14+
const renderWithProviders = (
15+
ui: React.ReactNode,
16+
{ renderOptions = {} } = {}
17+
) => {
18+
// You can wrap the component with any context providers here
19+
return render(ui, {
20+
wrapper: ({ children }) => (
21+
<ChakraProvider>
22+
<Formik initialValues={{}} onSubmit={() => {}}>
23+
{children}
24+
</Formik>
25+
</ChakraProvider>
26+
),
27+
...renderOptions
28+
});
29+
};
30+
31+
describe('PluginBox', () => {
32+
it.only('renders ErrorBox when editSchema is null', () => {
33+
mockPlugin.editSchema.mockReturnValue(null);
34+
35+
const { getByTestId } = renderWithProviders(
36+
<Formik initialValues={{}} onSubmit={() => {}}>
37+
<PluginBox plugin={mockPlugin as any}>
38+
{({ field }) => <div>{field.label}</div>}
39+
</PluginBox>
40+
</Formik>
41+
);
42+
43+
expect(getByTestId('plugin-box-error')).toMatchSnapshot();
44+
});
45+
46+
it('renders nothing when editSchema is Plugin.HIDDEN', () => {
47+
mockPlugin.editSchema.mockReturnValue('HIDDEN');
48+
49+
const { container } = renderWithProviders(
50+
<Formik initialValues={{}} onSubmit={() => {}}>
51+
<PluginBox plugin={mockPlugin as any}>
52+
{({ field }) => <div>{field.label}</div>}
53+
</PluginBox>
54+
</Formik>
55+
);
56+
57+
expect(container.firstChild).toBeNull();
58+
});
59+
60+
it('renders children when editSchema is valid', () => {
61+
const mockSchema = { type: 'string', label: 'testField' };
62+
mockPlugin.editSchema.mockReturnValue(mockSchema);
63+
64+
const { getByText } = renderWithProviders(
65+
<Formik initialValues={{}} onSubmit={() => {}}>
66+
<PluginBox plugin={mockPlugin as any}>
67+
{({ field }) => <div>{field.label}</div>}
68+
</PluginBox>
69+
</Formik>
70+
);
71+
72+
expect(getByText(/testField/i)).toBeInTheDocument();
73+
});
74+
});

packages/data-core/lib/components/plugin-box.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function PluginBox(props: PluginBoxProps) {
2727

2828
if (!editSchema) {
2929
return (
30-
<ErrorBox>
30+
<ErrorBox data-testid='plugin-box-error'>
3131
Plugin <Code color='red'>{plugin.name}</Code> has no edit schema.
3232
</ErrorBox>
3333
);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { ChakraProvider } from '@chakra-ui/react';
4+
5+
import { WidgetRenderer } from './widget-renderer';
6+
import { usePluginConfig } from '../context/plugin-config';
7+
import { SchemaField } from '../schema/types';
8+
9+
jest.mock('../context/plugin-config', () => ({
10+
usePluginConfig: jest.fn()
11+
}));
12+
13+
const mockPluginConfig = {
14+
'ui:widget': {
15+
text: ({ pointer }: { pointer: string }) => (
16+
<div>Text Widget: {pointer}</div>
17+
),
18+
number: ({ pointer }: { pointer: string }) => (
19+
<div>Number Widget: {pointer}</div>
20+
),
21+
radio: ({ pointer }: { pointer: string }) => (
22+
<div>Radio Widget: {pointer}</div>
23+
),
24+
broken: () => {
25+
throw new Error('Widget failed');
26+
}
27+
}
28+
};
29+
30+
describe('WidgetRenderer', () => {
31+
beforeEach(() => {
32+
(usePluginConfig as jest.Mock).mockReturnValue(mockPluginConfig);
33+
});
34+
35+
it('renders a text widget', () => {
36+
const field: SchemaField = { type: 'string' };
37+
render(<WidgetRenderer pointer='test.pointer' field={field} />);
38+
expect(screen.getByText('Text Widget: test.pointer')).toBeInTheDocument();
39+
});
40+
41+
it('renders a number widget', () => {
42+
const field: SchemaField = { type: 'number' };
43+
render(<WidgetRenderer pointer='test.pointer' field={field} />);
44+
expect(screen.getByText('Number Widget: test.pointer')).toBeInTheDocument();
45+
});
46+
47+
it('renders a radio widget for enum strings', () => {
48+
const field: SchemaField = {
49+
type: 'string',
50+
enum: [
51+
['option1', 'Option 1'],
52+
['option2', 'Option 2']
53+
]
54+
};
55+
render(<WidgetRenderer pointer='test.pointer' field={field} />);
56+
expect(screen.getByText('Radio Widget: test.pointer')).toBeInTheDocument();
57+
});
58+
59+
it('renders an error box when widget is not found', () => {
60+
const field: SchemaField = { type: 'string', 'ui:widget': 'custom' };
61+
render(
62+
<ChakraProvider>
63+
<WidgetRenderer pointer='test.pointer' field={field} />
64+
</ChakraProvider>
65+
);
66+
expect(screen.getByText('Widget "custom" not found')).toBeInTheDocument();
67+
});
68+
69+
it('renders error boundary when widget throws an error', () => {
70+
// The test will pass but there will be some noise in the output:
71+
// Error: Uncaught [Error: Widget failed]
72+
// So we need to spyOn the console error:
73+
jest.spyOn(console, 'error').mockImplementation(() => null);
74+
75+
const field: SchemaField = { type: 'string', 'ui:widget': 'broken' };
76+
render(
77+
<ChakraProvider>
78+
<WidgetRenderer pointer='test.pointer' field={field} />
79+
</ChakraProvider>
80+
);
81+
expect(
82+
screen.getByText('💔 Error rendering widget (broken)')
83+
).toBeInTheDocument();
84+
expect(screen.getByText('Widget failed')).toBeInTheDocument();
85+
86+
// Restore the original console.error to avoid affecting other tests.
87+
jest.spyOn(console, 'error').mockRestore();
88+
});
89+
});

packages/data-core/lib/components/widget-renderer.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from 'react';
2+
import { Box, Text } from '@chakra-ui/react';
23

34
import { usePluginConfig } from '../context/plugin-config';
45
import { ErrorBox } from './error-box';
56
import { SchemaField } from '../schema/types';
6-
import { Box, Text } from '@chakra-ui/react';
77

88
interface WidgetProps {
99
pointer: string;
@@ -26,7 +26,7 @@ export function WidgetRenderer(props: WidgetProps) {
2626
const Widget = config['ui:widget'][widget];
2727

2828
return (
29-
<WidgetErrorBoundary field={field} widget={widget} pointer={pointer}>
29+
<WidgetErrorBoundary field={field} widgetName={widget} pointer={pointer}>
3030
{Widget ? (
3131
<Widget pointer={pointer} field={field} isRequired={isRequired} />
3232
) : (
@@ -75,7 +75,7 @@ export function WidgetRenderer(props: WidgetProps) {
7575
interface WidgetErrorBoundaryProps {
7676
children: React.ReactNode;
7777
field: SchemaField;
78-
widget: string;
78+
widgetName: string;
7979
pointer: string;
8080
}
8181

@@ -101,7 +101,7 @@ class WidgetErrorBoundary extends React.Component<
101101
return (
102102
<ErrorBox color='base.500' alignItems='left' p={4}>
103103
<Text textTransform='uppercase' color='red.500'>
104-
💔 Error rendering widget ({this.props.widget})
104+
💔 Error rendering widget ({this.props.widgetName})
105105
</Text>
106106
<Text>
107107
{this.state.error.message || 'Something is wrong with this widget'}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { extendPluginConfig } from './index';
2+
import { PluginConfig } from './index';
3+
4+
describe('extendPluginConfig', () => {
5+
it('should merge multiple configurations', () => {
6+
const config1: Partial<PluginConfig> = {
7+
collectionPlugins: [{ name: 'plugin1' } as any],
8+
itemPlugins: [{ name: 'itemPlugin1' } as any],
9+
'ui:widget': { widget1: () => null }
10+
};
11+
12+
const config2: Partial<PluginConfig> = {
13+
collectionPlugins: [{ name: 'plugin2' } as any],
14+
'ui:widget': { widget2: () => null }
15+
};
16+
17+
const result = extendPluginConfig(config1, config2);
18+
19+
expect(result).toEqual({
20+
collectionPlugins: [{ name: 'plugin1' }, { name: 'plugin2' }],
21+
itemPlugins: [{ name: 'itemPlugin1' }],
22+
'ui:widget': {
23+
widget1: expect.any(Function),
24+
widget2: expect.any(Function)
25+
}
26+
});
27+
});
28+
29+
it('should handle empty configurations', () => {
30+
const result = extendPluginConfig();
31+
expect(result).toEqual({
32+
collectionPlugins: [],
33+
itemPlugins: [],
34+
'ui:widget': {}
35+
});
36+
});
37+
38+
it('should override properties with later configurations', () => {
39+
const config1: Partial<PluginConfig> = {
40+
'ui:widget': { widget1: () => 'old' }
41+
};
42+
43+
const config2: Partial<PluginConfig> = {
44+
'ui:widget': { widget1: () => 'new' }
45+
};
46+
47+
const result = extendPluginConfig(config1, config2);
48+
49+
expect(result['ui:widget'].widget1({} as any)).toBe('new');
50+
});
51+
});

0 commit comments

Comments
 (0)