Skip to content

Setup tests for different utility functions and components #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom';
4 changes: 2 additions & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const config: Config = {
// ],

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

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

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

// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@types/rollup-plugin-peer-deps-external": "^2.2.5",
"@types/testing-library__jest-dom": "^5.14.9",
"babel-jest": "^29.7.0",
"eslint": "^9.13.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PluginBox renders ErrorBox when editSchema is null 1`] = `
<div
class="css-133ygyc"
data-testid="plugin-box-error"
>
Plugin
<code
class="chakra-code css-1fu8sp0"
>
TestPlugin
</code>
has no edit schema.
</div>
`;
74 changes: 74 additions & 0 deletions packages/data-core/lib/components/plugin-box.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Formik } from 'formik';
import { ChakraProvider } from '@chakra-ui/react';

import { PluginBox } from './plugin-box';

const mockPlugin = {
name: 'TestPlugin',
editSchema: jest.fn()
};

// Custom renderer to add context or providers if needed
const renderWithProviders = (
ui: React.ReactNode,
{ renderOptions = {} } = {}
) => {
// You can wrap the component with any context providers here
return render(ui, {
wrapper: ({ children }) => (
<ChakraProvider>
<Formik initialValues={{}} onSubmit={() => {}}>
{children}
</Formik>
</ChakraProvider>
),
...renderOptions
});
};

describe('PluginBox', () => {
it.only('renders ErrorBox when editSchema is null', () => {
mockPlugin.editSchema.mockReturnValue(null);

const { getByTestId } = renderWithProviders(
<Formik initialValues={{}} onSubmit={() => {}}>
<PluginBox plugin={mockPlugin as any}>
{({ field }) => <div>{field.label}</div>}
</PluginBox>
</Formik>
);

expect(getByTestId('plugin-box-error')).toMatchSnapshot();
});

it('renders nothing when editSchema is Plugin.HIDDEN', () => {
mockPlugin.editSchema.mockReturnValue('HIDDEN');

const { container } = renderWithProviders(
<Formik initialValues={{}} onSubmit={() => {}}>
<PluginBox plugin={mockPlugin as any}>
{({ field }) => <div>{field.label}</div>}
</PluginBox>
</Formik>
);

expect(container.firstChild).toBeNull();
});

it('renders children when editSchema is valid', () => {
const mockSchema = { type: 'string', label: 'testField' };
mockPlugin.editSchema.mockReturnValue(mockSchema);

const { getByText } = renderWithProviders(
<Formik initialValues={{}} onSubmit={() => {}}>
<PluginBox plugin={mockPlugin as any}>
{({ field }) => <div>{field.label}</div>}
</PluginBox>
</Formik>
);

expect(getByText(/testField/i)).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion packages/data-core/lib/components/plugin-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function PluginBox(props: PluginBoxProps) {

if (!editSchema) {
return (
<ErrorBox>
<ErrorBox data-testid='plugin-box-error'>
Plugin <Code color='red'>{plugin.name}</Code> has no edit schema.
</ErrorBox>
);
Expand Down
89 changes: 89 additions & 0 deletions packages/data-core/lib/components/widget-renderer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ChakraProvider } from '@chakra-ui/react';

import { WidgetRenderer } from './widget-renderer';
import { usePluginConfig } from '../context/plugin-config';
import { SchemaField } from '../schema/types';

jest.mock('../context/plugin-config', () => ({
usePluginConfig: jest.fn()
}));

const mockPluginConfig = {
'ui:widget': {
text: ({ pointer }: { pointer: string }) => (
<div>Text Widget: {pointer}</div>
),
number: ({ pointer }: { pointer: string }) => (
<div>Number Widget: {pointer}</div>
),
radio: ({ pointer }: { pointer: string }) => (
<div>Radio Widget: {pointer}</div>
),
broken: () => {
throw new Error('Widget failed');
}
}
};

describe('WidgetRenderer', () => {
beforeEach(() => {
(usePluginConfig as jest.Mock).mockReturnValue(mockPluginConfig);
});

it('renders a text widget', () => {
const field: SchemaField = { type: 'string' };
render(<WidgetRenderer pointer='test.pointer' field={field} />);
expect(screen.getByText('Text Widget: test.pointer')).toBeInTheDocument();
});

it('renders a number widget', () => {
const field: SchemaField = { type: 'number' };
render(<WidgetRenderer pointer='test.pointer' field={field} />);
expect(screen.getByText('Number Widget: test.pointer')).toBeInTheDocument();
});

it('renders a radio widget for enum strings', () => {
const field: SchemaField = {
type: 'string',
enum: [
['option1', 'Option 1'],
['option2', 'Option 2']
]
};
render(<WidgetRenderer pointer='test.pointer' field={field} />);
expect(screen.getByText('Radio Widget: test.pointer')).toBeInTheDocument();
});

it('renders an error box when widget is not found', () => {
const field: SchemaField = { type: 'string', 'ui:widget': 'custom' };
render(
<ChakraProvider>
<WidgetRenderer pointer='test.pointer' field={field} />
</ChakraProvider>
);
expect(screen.getByText('Widget "custom" not found')).toBeInTheDocument();
});

it('renders error boundary when widget throws an error', () => {
// The test will pass but there will be some noise in the output:
// Error: Uncaught [Error: Widget failed]
// So we need to spyOn the console error:
jest.spyOn(console, 'error').mockImplementation(() => null);

const field: SchemaField = { type: 'string', 'ui:widget': 'broken' };
render(
<ChakraProvider>
<WidgetRenderer pointer='test.pointer' field={field} />
</ChakraProvider>
);
expect(
screen.getByText('💔 Error rendering widget (broken)')
).toBeInTheDocument();
expect(screen.getByText('Widget failed')).toBeInTheDocument();

// Restore the original console.error to avoid affecting other tests.
jest.spyOn(console, 'error').mockRestore();
});
});
8 changes: 4 additions & 4 deletions packages/data-core/lib/components/widget-renderer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { Box, Text } from '@chakra-ui/react';

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

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

return (
<WidgetErrorBoundary field={field} widget={widget} pointer={pointer}>
<WidgetErrorBoundary field={field} widgetName={widget} pointer={pointer}>
{Widget ? (
<Widget pointer={pointer} field={field} isRequired={isRequired} />
) : (
Expand Down Expand Up @@ -75,7 +75,7 @@ export function WidgetRenderer(props: WidgetProps) {
interface WidgetErrorBoundaryProps {
children: React.ReactNode;
field: SchemaField;
widget: string;
widgetName: string;
pointer: string;
}

Expand All @@ -101,7 +101,7 @@ class WidgetErrorBoundary extends React.Component<
return (
<ErrorBox color='base.500' alignItems='left' p={4}>
<Text textTransform='uppercase' color='red.500'>
💔 Error rendering widget ({this.props.widget})
💔 Error rendering widget ({this.props.widgetName})
</Text>
<Text>
{this.state.error.message || 'Something is wrong with this widget'}
Expand Down
51 changes: 51 additions & 0 deletions packages/data-core/lib/config/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { extendPluginConfig } from './index';
import { PluginConfig } from './index';

describe('extendPluginConfig', () => {
it('should merge multiple configurations', () => {
const config1: Partial<PluginConfig> = {
collectionPlugins: [{ name: 'plugin1' } as any],
itemPlugins: [{ name: 'itemPlugin1' } as any],
'ui:widget': { widget1: () => null }
};

const config2: Partial<PluginConfig> = {
collectionPlugins: [{ name: 'plugin2' } as any],
'ui:widget': { widget2: () => null }
};

const result = extendPluginConfig(config1, config2);

expect(result).toEqual({
collectionPlugins: [{ name: 'plugin1' }, { name: 'plugin2' }],
itemPlugins: [{ name: 'itemPlugin1' }],
'ui:widget': {
widget1: expect.any(Function),
widget2: expect.any(Function)
}
});
});

it('should handle empty configurations', () => {
const result = extendPluginConfig();
expect(result).toEqual({
collectionPlugins: [],
itemPlugins: [],
'ui:widget': {}
});
});

it('should override properties with later configurations', () => {
const config1: Partial<PluginConfig> = {
'ui:widget': { widget1: () => 'old' }
};

const config2: Partial<PluginConfig> = {
'ui:widget': { widget1: () => 'new' }
};

const result = extendPluginConfig(config1, config2);

expect(result['ui:widget'].widget1({} as any)).toBe('new');
});
});
2 changes: 1 addition & 1 deletion packages/data-core/lib/context/plugin-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const usePluginConfig = () => {

if (!context) {
throw new Error(
'usePluginConfig must be used within a PluginConfigContextProvider'
'usePluginConfig must be used within a PluginConfigProvider'
);
}

Expand Down
Loading