- {items.map((item, i) => (
+ {items.map((_, i) => (
value: {formData[i]}
))}
@@ -328,7 +317,9 @@ describe('ArrayFieldTemplate', () => {
formData,
templates: { ArrayFieldTemplate },
});
- Simulate.click(node.querySelector('.rjsf-array-item-add'));
+ const button = node.querySelector('.rjsf-array-item-add');
+ expect(button).toBeInTheDocument();
+ fireEvent.click(button!);
});
});
});
diff --git a/packages/core/test/BooleanField.test.jsx b/packages/core/test/BooleanField.test.tsx
similarity index 69%
rename from packages/core/test/BooleanField.test.jsx
rename to packages/core/test/BooleanField.test.tsx
index 515094ebec..f4fdc237bf 100644
--- a/packages/core/test/BooleanField.test.jsx
+++ b/packages/core/test/BooleanField.test.tsx
@@ -1,23 +1,11 @@
-import { expect } from 'chai';
import { fireEvent, act } from '@testing-library/react';
-import sinon from 'sinon';
+import { RJSFSchema, WidgetProps } from '@rjsf/utils';
-import { createFormComponent, createSandbox, getSelectedOptionValue, submitForm } from './test_utils';
+import { createFormComponent, getSelectedOptionValue, submitForm } from './testUtils';
-describe('BooleanField', () => {
- let sandbox;
-
- const CustomWidget = () =>
;
-
- beforeEach(() => {
- sandbox = createSandbox();
- sandbox.stub(console, 'warn');
- });
-
- afterEach(() => {
- sandbox.restore();
- });
+const CustomWidget = () =>
;
+describe('BooleanField', () => {
it('should render a boolean field', () => {
const { node } = createFormComponent({
schema: {
@@ -25,7 +13,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelectorAll('.rjsf-field input[type=checkbox]')).to.have.length.of(1);
+ expect(node.querySelectorAll('.rjsf-field input[type=checkbox]')).toHaveLength(1);
});
it('should render a boolean field with the expected id', () => {
@@ -35,7 +23,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('.rjsf-field input[type=checkbox]').id).eql('root');
+ expect(node.querySelector('.rjsf-field input[type=checkbox]')).toHaveAttribute('id', 'root');
});
it('should render a boolean field with a label', () => {
@@ -46,7 +34,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('.rjsf-field label span').textContent).eql('foo');
+ expect(node.querySelector('.rjsf-field label span')).toHaveTextContent('foo');
});
describe('HTML5 required attribute', () => {
@@ -63,7 +51,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(false);
+ expect(node.querySelector('input[type=checkbox]')).not.toHaveAttribute('required');
});
it('should add a required attribute if the schema uses const with a true value', () => {
@@ -79,7 +67,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(true);
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('required', '');
});
it('should add a required attribute if the schema uses an enum with a single value of true', () => {
@@ -95,7 +83,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(true);
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('required', '');
});
it('should add a required attribute if the schema uses an anyOf with a single value of true', () => {
@@ -115,7 +103,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(true);
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('required', '');
});
it('should add a required attribute if the schema uses a oneOf with a single value of true', () => {
@@ -135,7 +123,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(true);
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('required', '');
});
it('should add a required attribute if the schema uses an allOf with a value of true', () => {
@@ -155,7 +143,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').required).eql(true);
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('required', '');
});
});
@@ -167,7 +155,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelectorAll('.rjsf-field label')).to.have.length.of(1);
+ expect(node.querySelectorAll('.rjsf-field label')).toHaveLength(1);
});
it('should render a description', () => {
@@ -179,12 +167,12 @@ describe('BooleanField', () => {
});
const description = node.querySelector('.field-description');
- expect(description.textContent).eql('my description');
+ expect(description).toHaveTextContent('my description');
});
it('should pass uiSchema to custom widget', () => {
- const CustomCheckboxWidget = ({ uiSchema }) => {
- return
{uiSchema.custom_field_key['ui:options'].test}
;
+ const CustomCheckboxWidget = ({ uiSchema }: WidgetProps) => {
+ return
{uiSchema?.custom_field_key['ui:options'].test}
;
};
const { node } = createFormComponent({
@@ -205,7 +193,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('#custom-ui-option-value').textContent).to.eql('foo');
+ expect(node.querySelector('#custom-ui-option-value')).toHaveTextContent('foo');
});
it('should render the description using provided description field', () => {
@@ -222,7 +210,7 @@ describe('BooleanField', () => {
});
const description = node.querySelector('.field-description');
- expect(description.textContent).eql('my description overridden');
+ expect(description).toHaveTextContent('my description overridden');
});
it('should assign a default value', () => {
@@ -233,7 +221,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('.rjsf-field input').checked).eql(true);
+ expect(node.querySelector('.rjsf-field input')).toHaveAttribute('checked', '');
});
it('formData should default to undefined', () => {
@@ -242,9 +230,10 @@ describe('BooleanField', () => {
noValidate: true,
});
submitForm(node);
- sinon.assert.calledWithMatch(onSubmit.lastCall, {
- formData: undefined,
- });
+ expect(onSubmit).toHaveBeenLastCalledWith(
+ expect.objectContaining({ formData: undefined }),
+ expect.objectContaining({ type: 'submit' }),
+ );
});
it('should focus on required radio missing data when focusOnFirstField and shows error', () => {
@@ -261,22 +250,22 @@ describe('BooleanField', () => {
focusOnFirstError: true,
uiSchema: { bool: { 'ui:widget': 'radio' } },
});
- const focusSpys = [sinon.spy(), sinon.spy()];
+ const focusSpys = [jest.fn(), jest.fn()];
const inputs = node.querySelectorAll('input[id^=root_bool]');
- expect(inputs.length).eql(2);
+ expect(inputs).toHaveLength(2);
let errorInputs = node.querySelectorAll('.form-group.rjsf-field-error input[id^=root_bool]');
- expect(errorInputs).to.have.length.of(0);
+ expect(errorInputs).toHaveLength(0);
// Since programmatically triggering focus does not call onFocus, change the focus method to a spy
- inputs[0].focus = focusSpys[0];
- inputs[1].focus = focusSpys[1];
+ (inputs[0] as HTMLInputElement).focus = focusSpys[0];
+ (inputs[1] as HTMLInputElement).focus = focusSpys[1];
submitForm(node);
- sinon.assert.calledWithMatch(onError.lastCall, {
- formData: undefined,
- });
- sinon.assert.calledOnce(focusSpys[0]);
- sinon.assert.notCalled(focusSpys[1]);
+ expect(onError).toHaveBeenLastCalledWith([
+ expect.objectContaining({ message: "must have required property 'bool'" }),
+ ]);
+ expect(focusSpys[0]).toHaveBeenCalled();
+ expect(focusSpys[1]).not.toHaveBeenCalled();
errorInputs = node.querySelectorAll('.form-group.rjsf-field-error input[id^=root_bool]');
- expect(errorInputs).to.have.length.of(2);
+ expect(errorInputs).toHaveLength(2);
});
it('should focus on required radio missing data when focusOnFirstField and hides error', () => {
@@ -293,22 +282,22 @@ describe('BooleanField', () => {
focusOnFirstError: true,
uiSchema: { bool: { 'ui:widget': 'radio', 'ui:hideError': true } },
});
- const focusSpys = [sinon.spy(), sinon.spy()];
+ const focusSpys = [jest.fn(), jest.fn()];
const inputs = node.querySelectorAll('input[id^=root_bool]');
- expect(inputs.length).eql(2);
+ expect(inputs).toHaveLength(2);
let errorInputs = node.querySelectorAll('.form-group.rjsf-field-error input[id^=root_bool]');
- expect(errorInputs).to.have.length.of(0);
+ expect(errorInputs).toHaveLength(0);
// Since programmatically triggering focus does not call onFocus, change the focus method to a spy
- inputs[0].focus = focusSpys[0];
- inputs[1].focus = focusSpys[1];
+ (inputs[0] as HTMLInputElement).focus = focusSpys[0];
+ (inputs[1] as HTMLInputElement).focus = focusSpys[1];
submitForm(node);
- sinon.assert.calledWithMatch(onError.lastCall, {
- formData: undefined,
- });
- sinon.assert.calledOnce(focusSpys[0]);
- sinon.assert.notCalled(focusSpys[1]);
+ expect(onError).toHaveBeenLastCalledWith([
+ expect.objectContaining({ message: "must have required property 'bool'" }),
+ ]);
+ expect(focusSpys[0]).toHaveBeenCalled();
+ expect(focusSpys[1]).not.toHaveBeenCalled();
errorInputs = node.querySelectorAll('.form-group.rjsf-field-error input[id^=root_bool]');
- expect(errorInputs).to.have.length.of(0);
+ expect(errorInputs).toHaveLength(0);
});
it('should handle a change event', () => {
@@ -320,10 +309,10 @@ describe('BooleanField', () => {
});
act(() => {
- fireEvent.click(node.querySelector('input'));
+ fireEvent.click(node.querySelector('input')!);
});
- sinon.assert.calledWithMatch(onChange.lastCall, { formData: true }, 'root');
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: true }), 'root');
});
it('should fill field with data', () => {
@@ -334,7 +323,7 @@ describe('BooleanField', () => {
formData: true,
});
- expect(node.querySelector('.rjsf-field input').checked).eql(true);
+ expect(node.querySelector('.rjsf-field input')).toHaveAttribute('checked', '');
});
it('should render radio widgets with the expected id', () => {
@@ -345,7 +334,7 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio' },
});
- expect(node.querySelector('.field-radio-group').id).eql('root');
+ expect(node.querySelector('.field-radio-group')).toHaveAttribute('id', 'root');
});
it('should have default enum option labels for radio widgets', () => {
@@ -357,8 +346,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio' },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['Yes', 'No']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['Yes', 'No']);
});
it('should support enum option ordering for radio widgets', () => {
@@ -371,8 +363,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio' },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['No', 'Yes']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['No', 'Yes']);
});
it('should support ui:enumNames for radio widgets', () => {
@@ -382,8 +377,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio', 'ui:enumNames': ['Yes', 'No'] },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['Yes', 'No']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['Yes', 'No']);
});
it('should support oneOf titles for radio widgets', () => {
@@ -405,8 +403,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio' },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['Yes', 'No']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['Yes', 'No']);
});
it('should support oneOf titles for radio widgets, overrides in uiSchema', () => {
@@ -428,8 +429,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio', oneOf: [{ 'ui:title': 'Si!' }, { 'ui:title': 'No!' }] },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['Si!', 'No!']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['Si!', 'No!']);
});
it('should preserve oneOf option ordering for radio widgets', () => {
@@ -451,8 +455,11 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'radio' },
});
- const labels = [].map.call(node.querySelectorAll('.field-radio-group label'), (label) => label.textContent);
- expect(labels).eql(['No', 'Yes']);
+ const labels = [].map.call(
+ node.querySelectorAll('.field-radio-group label'),
+ (label: Element) => label.textContent,
+ );
+ expect(labels).toEqual(['No', 'Yes']);
});
it('should support inline radio widgets', () => {
@@ -467,11 +474,11 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelectorAll('.radio-inline')).to.have.length.of(2);
+ expect(node.querySelectorAll('.radio-inline')).toHaveLength(2);
});
it('should handle a focus event for radio widgets', () => {
- const onFocus = sandbox.spy();
+ const onFocus = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -484,16 +491,16 @@ describe('BooleanField', () => {
});
const element = node.querySelector('.field-radio-group');
- fireEvent.focus(node.querySelector('input'), {
+ fireEvent.focus(node.querySelector('input')!, {
target: {
value: 1, // use index
},
});
- expect(onFocus.calledWith(element.id, false)).to.be.true;
+ expect(onFocus).toHaveBeenLastCalledWith(element?.id, false);
});
it('should handle a blur event for radio widgets', () => {
- const onBlur = sandbox.spy();
+ const onBlur = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -506,12 +513,12 @@ describe('BooleanField', () => {
});
const element = node.querySelector('.field-radio-group');
- fireEvent.blur(node.querySelector('input'), {
+ fireEvent.blur(node.querySelector('input')!, {
target: {
value: 1, // use index
},
});
- expect(onBlur.calledWith(element.id, false)).to.be.true;
+ expect(onBlur).toHaveBeenLastCalledWith(element?.id, false);
});
it('should support ui:enumNames for select, with overrides in uiSchema', () => {
@@ -521,12 +528,12 @@ describe('BooleanField', () => {
uiSchema: { 'ui:widget': 'select', 'ui:enumNames': ['Si!', 'No!'] },
});
- const labels = [].map.call(node.querySelectorAll('.rjsf-field option'), (label) => label.textContent);
- expect(labels).eql(['', 'Si!', 'No!']);
+ const labels = [].map.call(node.querySelectorAll('.rjsf-field option'), (label: Element) => label.textContent);
+ expect(labels).toEqual(['', 'Si!', 'No!']);
});
it('should handle a focus event with checkbox', () => {
- const onFocus = sandbox.spy();
+ const onFocus = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -539,16 +546,16 @@ describe('BooleanField', () => {
});
const element = node.querySelector('select');
- fireEvent.focus(element, {
+ fireEvent.focus(element!, {
target: {
value: 1, // use index
},
});
- expect(onFocus.calledWith(element.id, false)).to.be.true;
+ expect(onFocus).toHaveBeenLastCalledWith(element?.id, false);
});
it('should handle a blur event with select', () => {
- const onBlur = sandbox.spy();
+ const onBlur = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -561,12 +568,12 @@ describe('BooleanField', () => {
});
const element = node.querySelector('select');
- fireEvent.blur(element, {
+ fireEvent.blur(element!, {
target: {
value: 1, // use index
},
});
- expect(onBlur.calledWith(element.id, false)).to.be.true;
+ expect(onBlur).toHaveBeenLastCalledWith(element?.id, false);
});
it('should render the widget with the expected id', () => {
@@ -576,7 +583,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('input[type=checkbox]').id).eql('root');
+ expect(node.querySelector('input[type=checkbox]')).toHaveAttribute('id', 'root');
});
it('should render customized checkbox', () => {
@@ -589,11 +596,11 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('#custom')).to.exist;
+ expect(node.querySelector('#custom')).toBeInTheDocument();
});
it('should handle a focus event with checkbox', () => {
- const onFocus = sandbox.spy();
+ const onFocus = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -606,16 +613,16 @@ describe('BooleanField', () => {
});
const element = node.querySelector('input');
- fireEvent.focus(element, {
+ fireEvent.focus(element!, {
target: {
checked: false,
},
});
- expect(onFocus.calledWith(element.id, false)).to.be.true;
+ expect(onFocus).toHaveBeenLastCalledWith(element?.id, false);
});
it('should handle a blur event with checkbox', () => {
- const onBlur = sandbox.spy();
+ const onBlur = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'boolean',
@@ -628,21 +635,21 @@ describe('BooleanField', () => {
});
const element = node.querySelector('input');
- fireEvent.blur(element, {
+ fireEvent.blur(element!, {
target: {
checked: false,
},
});
- expect(onBlur.calledWith(element.id, false)).to.be.true;
+ expect(onBlur).toHaveBeenLastCalledWith(element?.id, false);
});
describe('Label', () => {
- const Widget = (props) =>
;
+ const Widget = (props: WidgetProps) =>
;
const widgets = { Widget };
it('should pass field name to widget if there is no title', () => {
- const schema = {
+ const schema: RJSFSchema = {
type: 'object',
properties: {
boolean: {
@@ -657,11 +664,11 @@ describe('BooleanField', () => {
};
const { node } = createFormComponent({ schema, widgets, uiSchema });
- expect(node.querySelector('#label-boolean')).to.not.be.null;
+ expect(node.querySelector('#label-boolean')).not.toBeNull();
});
it('should pass schema title to widget', () => {
- const schema = {
+ const schema: RJSFSchema = {
type: 'boolean',
title: 'test',
};
@@ -670,11 +677,11 @@ describe('BooleanField', () => {
};
const { node } = createFormComponent({ schema, widgets, uiSchema });
- expect(node.querySelector('#label-test')).to.not.be.null;
+ expect(node.querySelector('#label-test')).not.toBeNull();
});
it('should pass empty schema title to widget', () => {
- const schema = {
+ const schema: RJSFSchema = {
type: 'boolean',
title: '',
};
@@ -682,7 +689,7 @@ describe('BooleanField', () => {
'ui:widget': 'Widget',
};
const { node } = createFormComponent({ schema, widgets, uiSchema });
- expect(node.querySelector('#label-')).to.not.be.null;
+ expect(node.querySelector('#label-')).not.toBeNull();
});
});
@@ -694,30 +701,27 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelectorAll('.rjsf-field select')).to.have.length.of(1);
+ expect(node.querySelectorAll('.rjsf-field select')).toHaveLength(1);
});
it('should infer the value from an enum on change', () => {
- const spy = sinon.spy();
- const { node } = createFormComponent({
+ const { node, onChange } = createFormComponent({
schema: {
enum: [true, false],
},
- onChange: spy,
});
- expect(node.querySelectorAll('.rjsf-field select')).to.have.length.of(1);
+ expect(node.querySelectorAll('.rjsf-field select')).toHaveLength(1);
const $select = node.querySelector('.rjsf-field select');
- expect($select.value).eql('');
+ expect($select).toHaveValue('');
act(() => {
- fireEvent.change($select, {
+ fireEvent.change($select!, {
target: { value: 0 }, // use index
});
});
- expect(getSelectedOptionValue($select)).eql('true');
- expect(spy.lastCall.args[0].formData).eql(true);
- expect(spy.lastCall.args[1]).eql('root');
+ expect(getSelectedOptionValue($select as HTMLSelectElement)).toEqual('true');
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: true }), 'root');
});
it('should render a string field with a label', () => {
@@ -728,7 +732,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('.rjsf-field label').textContent).eql('foo');
+ expect(node.querySelector('.rjsf-field label')).toHaveTextContent('foo');
});
it('should assign a default value', () => {
@@ -738,9 +742,7 @@ describe('BooleanField', () => {
default: true,
},
});
- sinon.assert.calledWithMatch(onChange.lastCall, {
- formData: true,
- });
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: true }));
});
it('should handle a change event', () => {
@@ -751,18 +753,12 @@ describe('BooleanField', () => {
});
act(() => {
- fireEvent.change(node.querySelector('select'), {
+ fireEvent.change(node.querySelector('select')!, {
target: { value: 1 }, // use index
});
});
- sinon.assert.calledWithMatch(
- onChange.lastCall,
- {
- formData: false,
- },
- 'root',
- );
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: false }), 'root');
});
it('should render the widget with the expected id', () => {
@@ -772,7 +768,7 @@ describe('BooleanField', () => {
},
});
- expect(node.querySelector('select').id).eql('root');
+ expect(node.querySelector('select')).toHaveAttribute('id', 'root');
});
});
});
diff --git a/packages/core/test/DescriptionField.test.jsx b/packages/core/test/DescriptionField.test.jsx
deleted file mode 100644
index a7342d0d89..0000000000
--- a/packages/core/test/DescriptionField.test.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Component } from 'react';
-import { expect } from 'chai';
-
-import DescriptionField from '../src/components/templates/DescriptionField';
-import { createSandbox, createComponent } from './test_utils';
-
-describe('DescriptionField', () => {
- let sandbox;
-
- beforeEach(() => {
- sandbox = createSandbox();
- });
-
- afterEach(() => {
- sandbox.restore();
- });
-
- // For some reason, stateless components needs to be wrapped into a stateful
- // one to be rendered into the document.
- class DescriptionFieldWrapper extends Component {
- constructor(props) {
- super(props);
- }
- render() {
- return
;
- }
- }
-
- it('should return a div for a custom component', () => {
- const props = {
- description:
description,
- registry: {},
- };
- const { node } = createComponent(DescriptionFieldWrapper, props);
-
- expect(node.tagName).to.equal('DIV');
- });
-
- it('should return a p for a description text', () => {
- const props = {
- description: 'description',
- registry: {},
- };
- const { node } = createComponent(DescriptionFieldWrapper, props);
-
- expect(node.tagName).to.equal('DIV');
- });
-
- it('should have the expected id', () => {
- const props = {
- description: 'Field description',
- id: 'sample_id',
- registry: {},
- };
- const { node } = createComponent(DescriptionFieldWrapper, props);
-
- expect(node.id).to.equal('sample_id');
- });
-});
diff --git a/packages/core/test/DescriptionField.test.tsx b/packages/core/test/DescriptionField.test.tsx
new file mode 100644
index 0000000000..c92355e9c5
--- /dev/null
+++ b/packages/core/test/DescriptionField.test.tsx
@@ -0,0 +1,29 @@
+import { render } from '@testing-library/react';
+import { DescriptionFieldProps } from '@rjsf/utils';
+
+import DescriptionField from '../src/components/templates/DescriptionField';
+import { getTestRegistry } from '../src';
+
+const registry = getTestRegistry({});
+
+describe('DescriptionField', () => {
+ let node: Element;
+ let props: DescriptionFieldProps;
+ beforeAll(() => {
+ props = {
+ id: 'sample_id',
+ description: 'Field description',
+ schema: {},
+ registry,
+ };
+ const { container } = render(
);
+ node = container.firstElementChild!;
+ });
+ it('should return a div for a custom component', () => {
+ expect(node.tagName).toEqual('DIV');
+ });
+
+ it('should have the expected id', () => {
+ expect(node).toHaveAttribute('id', 'sample_id');
+ });
+});
diff --git a/packages/core/test/FieldTemplate.test.jsx b/packages/core/test/FieldTemplate.test.tsx
similarity index 78%
rename from packages/core/test/FieldTemplate.test.jsx
rename to packages/core/test/FieldTemplate.test.tsx
index 22e6b7f38d..51bed7d23b 100644
--- a/packages/core/test/FieldTemplate.test.jsx
+++ b/packages/core/test/FieldTemplate.test.tsx
@@ -1,21 +1,11 @@
import { Children } from 'react';
+import { FieldTemplateProps, RJSFSchema } from '@rjsf/utils';
-import { expect } from 'chai';
-import { createFormComponent, createSandbox } from './test_utils';
+import { createFormComponent } from './testUtils';
describe('FieldTemplate', () => {
- let sandbox;
-
- beforeEach(() => {
- sandbox = createSandbox();
- });
-
- afterEach(() => {
- sandbox.restore();
- });
-
describe('FieldTemplate should only have one child', () => {
- function FieldTemplate(props) {
+ function FieldTemplate(props: FieldTemplateProps) {
if (Children.count(props.children) !== 1) {
throw 'Got wrong number of children';
}
@@ -29,7 +19,7 @@ describe('FieldTemplate', () => {
});
describe('Custom FieldTemplate for disabled property', () => {
- function FieldTemplate(props) {
+ function FieldTemplate(props: FieldTemplateProps) {
return
;
}
@@ -40,7 +30,7 @@ describe('FieldTemplate', () => {
uiSchema: { 'ui:disabled': true },
templates: { FieldTemplate },
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(1);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(1);
});
it('should render with disabled when ui:disabled is falsey', () => {
@@ -49,7 +39,7 @@ describe('FieldTemplate', () => {
uiSchema: { 'ui:disabled': false },
templates: { FieldTemplate },
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(0);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(0);
});
});
describe('with template configured in ui:FieldTemplate', () => {
@@ -58,7 +48,7 @@ describe('FieldTemplate', () => {
schema: { type: 'string' },
uiSchema: { 'ui:disabled': true, 'ui:FieldTemplate': FieldTemplate },
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(1);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(1);
});
it('should render with disabled when ui:disabled is falsey', () => {
@@ -66,7 +56,7 @@ describe('FieldTemplate', () => {
schema: { type: 'string' },
uiSchema: { 'ui:disabled': false, 'ui:FieldTemplate': FieldTemplate },
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(0);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(0);
});
});
describe('with template configured globally being overriden in ui:FieldTemplate', () => {
@@ -77,7 +67,7 @@ describe('FieldTemplate', () => {
// Empty field template to prove that overides work
templates: { FieldTemplate: () =>
},
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(1);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(1);
});
it('should render with disabled when ui:disabled is falsey', () => {
@@ -87,13 +77,13 @@ describe('FieldTemplate', () => {
// Empty field template to prove that overides work
templates: { FieldTemplate: () =>
},
});
- expect(node.querySelectorAll('.disabled')).to.have.length.of(0);
+ expect(node.querySelectorAll('.disabled')).toHaveLength(0);
});
});
});
describe('Custom FieldTemplate should have registry', () => {
- function FieldTemplate(props) {
+ function FieldTemplate(props: FieldTemplateProps) {
return (
Root Schema:
{JSON.stringify(props.registry.rootSchema)}
@@ -102,7 +92,7 @@ describe('FieldTemplate', () => {
}
it('should allow access to root schema from registry', () => {
- const schema = {
+ const schema: RJSFSchema = {
type: 'object',
properties: { fooBarBaz: { type: 'string' } },
};
@@ -112,8 +102,8 @@ describe('FieldTemplate', () => {
templates: { FieldTemplate },
});
- expect(node.querySelectorAll('#root-schema')).to.have.length.of(1);
- expect(node.querySelectorAll('#root-schema')[0].innerHTML).to.equal(JSON.stringify(schema));
+ expect(node.querySelectorAll('#root-schema')).toHaveLength(1);
+ expect(node.querySelectorAll('#root-schema')[0].innerHTML).toEqual(JSON.stringify(schema));
});
});
});
diff --git a/packages/core/test/FormContext.test.jsx b/packages/core/test/FormContext.test.tsx
similarity index 66%
rename from packages/core/test/FormContext.test.jsx
rename to packages/core/test/FormContext.test.tsx
index 41ef46b2ac..db0c491207 100644
--- a/packages/core/test/FormContext.test.jsx
+++ b/packages/core/test/FormContext.test.tsx
@@ -1,64 +1,45 @@
-import { expect } from 'chai';
+import { ArrayFieldTemplateProps, FieldTemplateProps, RJSFSchema } from '@rjsf/utils';
-import { createFormComponent, createSandbox } from './test_utils';
+import { createFormComponent } from './testUtils';
-describe('FormContext', () => {
- let sandbox;
-
- beforeEach(() => {
- sandbox = createSandbox();
- });
+const schema: RJSFSchema = { type: 'string' };
- afterEach(() => {
- sandbox.restore();
- });
+const formContext = { foo: 'bar' };
- const schema = { type: 'string' };
+const fooId = `#${formContext.foo}`;
- const formContext = { foo: 'bar' };
-
- const CustomComponent = function (props) {
- const { registry } = props;
- const { formContext } = registry;
- return
;
- };
-
- it('should be passed to Form', () => {
- const { comp } = createFormComponent({
- schema: schema,
- formContext,
- });
- expect(comp.props.formContext).eq(formContext);
- });
+// Use `props: any` to support the variety of uses (widgets, fields, templates)
+function CustomComponent(props: any) {
+ const { registry } = props;
+ const { formContext } = registry;
+ return
;
+}
+describe('FormContext', () => {
it('should be passed to custom field', () => {
- const fields = { custom: CustomComponent };
-
const { node } = createFormComponent({
schema: schema,
uiSchema: { 'ui:field': 'custom' },
- fields,
+ fields: { custom: CustomComponent },
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to custom widget', () => {
- const widgets = { custom: CustomComponent };
-
const { node } = createFormComponent({
schema: { type: 'string' },
uiSchema: { 'ui:widget': 'custom' },
- widgets,
+ widgets: { custom: CustomComponent },
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to TemplateField', () => {
- function CustomTemplateField({ registry: { formContext } }) {
+ function CustomTemplateField({ registry: { formContext } }: FieldTemplateProps) {
return
;
}
@@ -75,11 +56,11 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to ArrayTemplateField', () => {
- function CustomArrayTemplateField({ registry: { formContext } }) {
+ function CustomArrayTemplateField({ registry: { formContext } }: ArrayFieldTemplateProps) {
return
;
}
@@ -94,7 +75,7 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to custom TitleFieldTemplate', () => {
@@ -114,7 +95,7 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to custom DescriptionFieldTemplate', () => {
@@ -126,7 +107,7 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to multiselect', () => {
@@ -149,7 +130,7 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
it('should be passed to files array', () => {
@@ -166,6 +147,6 @@ describe('FormContext', () => {
formContext,
});
- expect(node.querySelector('#' + formContext.foo)).to.exist;
+ expect(node.querySelector(fooId)).toBeInTheDocument();
});
});
diff --git a/packages/core/test/NullField.test.jsx b/packages/core/test/NullField.test.tsx
similarity index 61%
rename from packages/core/test/NullField.test.jsx
rename to packages/core/test/NullField.test.tsx
index d4eb92ea3b..be884abeb4 100644
--- a/packages/core/test/NullField.test.jsx
+++ b/packages/core/test/NullField.test.tsx
@@ -1,18 +1,6 @@
-import { expect } from 'chai';
-import { createFormComponent, createSandbox, submitForm } from './test_utils';
-import sinon from 'sinon';
+import { createFormComponent, submitForm } from './testUtils';
describe('NullField', () => {
- let sandbox;
-
- beforeEach(() => {
- sandbox = createSandbox();
- });
-
- afterEach(() => {
- sandbox.restore();
- });
-
describe('No widget', () => {
it('should render a null field', () => {
const { node } = createFormComponent({
@@ -21,7 +9,7 @@ describe('NullField', () => {
},
});
- expect(node.querySelectorAll('.rjsf-field')).to.have.length.of(1);
+ expect(node.querySelectorAll('.rjsf-field')).toHaveLength(1);
});
it('should render a null field with a label', () => {
@@ -32,7 +20,7 @@ describe('NullField', () => {
},
});
- expect(node.querySelector('.rjsf-field label').textContent).eql('foo');
+ expect(node.querySelector('.rjsf-field label')).toHaveTextContent('foo');
});
it('should assign a default value', () => {
@@ -43,7 +31,7 @@ describe('NullField', () => {
},
});
- sinon.assert.calledWithMatch(onChange.lastCall, { formData: null });
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: null }));
});
it('should not overwrite existing data', () => {
@@ -56,7 +44,10 @@ describe('NullField', () => {
});
submitForm(node);
- sinon.assert.calledWithMatch(onSubmit.lastCall, { formData: 3 });
+ expect(onSubmit).toHaveBeenLastCalledWith(
+ expect.objectContaining({ formData: 3 }),
+ expect.objectContaining({ type: 'submit' }),
+ );
});
});
});
diff --git a/packages/core/test/NumberField.test.jsx b/packages/core/test/NumberField.test.tsx
similarity index 58%
rename from packages/core/test/NumberField.test.jsx
rename to packages/core/test/NumberField.test.tsx
index f3b28a7c20..77b0bec719 100644
--- a/packages/core/test/NumberField.test.jsx
+++ b/packages/core/test/NumberField.test.tsx
@@ -1,21 +1,15 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import { fireEvent, act } from '@testing-library/react';
-import sinon from 'sinon';
+import { createRef } from 'react';
+import { RJSFSchema, UiSchema } from '@rjsf/utils';
+import { act, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import isEmpty from 'lodash/isEmpty';
-import { createFormComponent, createSandbox, getSelectedOptionValue, setProps, submitForm } from './test_utils';
+import Form from '../src';
+import { createFormComponent, getSelectedOptionValue, submitForm } from './testUtils';
-describe('NumberField', () => {
- let sandbox;
-
- beforeEach(() => {
- sandbox = createSandbox();
- });
-
- afterEach(() => {
- sandbox.restore();
- });
+const user = userEvent.setup();
+describe('NumberField', () => {
describe('Number widget', () => {
it('should use step to represent the multipleOf keyword', () => {
const { node } = createFormComponent({
@@ -25,7 +19,7 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').step).to.eql('5');
+ expect(node.querySelector('input')).toHaveAttribute('step', '5');
});
it('should use min to represent the minimum keyword', () => {
@@ -36,7 +30,7 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').min).to.eql('0');
+ expect(node.querySelector('input')).toHaveAttribute('min', '0');
});
it('should use max to represent the maximum keyword', () => {
@@ -47,7 +41,7 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').max).to.eql('100');
+ expect(node.querySelector('input')).toHaveAttribute('max', '100');
});
it('should use step to represent the multipleOf keyword', () => {
@@ -58,7 +52,7 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').step).to.eql('5');
+ expect(node.querySelector('input')).toHaveAttribute('step', '5');
});
it('should use min to represent the minimum keyword', () => {
@@ -69,7 +63,7 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').min).to.eql('0');
+ expect(node.querySelector('input')).toHaveAttribute('min', '0');
});
it('should use max to represent the maximum keyword', () => {
@@ -80,11 +74,11 @@ describe('NumberField', () => {
},
});
- expect(node.querySelector('input').max).to.eql('100');
+ expect(node.querySelector('input')).toHaveAttribute('max', '100');
});
});
describe('Number and text widget', () => {
- let uiSchemas = [
+ const uiSchemas: UiSchema[] = [
{},
{
'ui:options': {
@@ -92,7 +86,7 @@ describe('NumberField', () => {
},
},
];
- for (let uiSchema of uiSchemas) {
+ for (const uiSchema of uiSchemas) {
it('should render a string field with a label', () => {
const { node } = createFormComponent({
schema: {
@@ -102,7 +96,7 @@ describe('NumberField', () => {
uiSchema,
});
- expect(node.querySelector('.rjsf-field label').textContent).eql('foo');
+ expect(node.querySelector('.rjsf-field label')).toHaveTextContent('foo');
});
it('should render a string field with a description', () => {
@@ -114,7 +108,7 @@ describe('NumberField', () => {
uiSchema,
});
- expect(node.querySelector('.field-description').textContent).eql('bar');
+ expect(node.querySelector('.field-description')).toHaveTextContent('bar');
});
it('formData should default to undefined', () => {
@@ -125,9 +119,10 @@ describe('NumberField', () => {
});
submitForm(node);
- sinon.assert.calledWithMatch(onSubmit.lastCall, {
- formData: undefined,
- });
+ expect(onSubmit).toHaveBeenLastCalledWith(
+ expect.objectContaining({ formData: undefined }),
+ expect.objectContaining({ type: 'submit' }),
+ );
});
it('should assign a default value', () => {
@@ -139,10 +134,10 @@ describe('NumberField', () => {
uiSchema,
});
- expect(node.querySelector('.rjsf-field input').value).eql('2');
+ expect(node.querySelector('.rjsf-field input')).toHaveAttribute('value', '2');
});
- it('should handle a change event', () => {
+ it('should handle a change event', async () => {
const { node, onChange } = createFormComponent({
schema: {
type: 'number',
@@ -150,23 +145,13 @@ describe('NumberField', () => {
uiSchema,
});
- act(() => {
- fireEvent.change(node.querySelector('input'), {
- target: { value: '2' },
- });
- });
+ await user.type(node.querySelector('input')!, '2');
- sinon.assert.calledWithMatch(
- onChange.lastCall,
- {
- formData: 2,
- },
- 'root',
- );
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: 2 }), 'root');
});
it('should handle a blur event', () => {
- const onBlur = sandbox.spy();
+ const onBlur = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'number',
@@ -176,15 +161,15 @@ describe('NumberField', () => {
});
const input = node.querySelector('input');
- fireEvent.blur(input, {
+ fireEvent.blur(input!, {
target: { value: '2' },
});
- expect(onBlur.calledWith(input.id, 2));
+ expect(onBlur).toHaveBeenCalledWith(input?.id, '2');
});
it('should handle a focus event', () => {
- const onFocus = sandbox.spy();
+ const onFocus = jest.fn();
const { node } = createFormComponent({
schema: {
type: 'number',
@@ -194,11 +179,11 @@ describe('NumberField', () => {
});
const input = node.querySelector('input');
- fireEvent.focus(input, {
+ fireEvent.focus(input!, {
target: { value: '2' },
});
- expect(onFocus.calledWith(input.id, 2));
+ expect(onFocus).toHaveBeenCalledWith(input?.id, '2');
});
it('should fill field with data', () => {
@@ -210,7 +195,7 @@ describe('NumberField', () => {
formData: 2,
});
- expect(node.querySelector('.rjsf-field input').value).eql('2');
+ expect(node.querySelector('.rjsf-field input')).toHaveAttribute('value', '2');
});
describe('when inputting a number that ends with a dot and/or zero it should normalize it, without changing the input value', () => {
@@ -258,7 +243,7 @@ describe('NumberField', () => {
];
tests.forEach((test) => {
- it(`should work with an input value of ${test.input}`, () => {
+ it(`should work with an input value of ${test.input}`, async () => {
const { node, onChange } = createFormComponent({
schema: {
type: 'number',
@@ -268,29 +253,17 @@ describe('NumberField', () => {
const $input = node.querySelector('input');
- act(() => {
- fireEvent.change($input, {
- target: { value: test.input },
- });
- });
+ await user.type($input!, test.input);
- setTimeout(() => {
- sinon.assert.calledWithMatch(
- onChange.lastCall,
- {
- formData: test.output,
- },
- 'root',
- );
- // "2." is not really a valid number in a input field of type number
- // so we need to use getAttribute("value") instead since .value outputs the empty string
- expect($input.getAttribute('value')).eql(test.input);
- }, 0);
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: test.output }), 'root');
+ // "2." is not really a valid number in a input field of type number
+ // so we need to use getAttribute("value") instead since .value outputs the empty string
+ expect($input).toHaveValue(isEmpty(uiSchema) ? test.output : test.input);
});
});
});
- it('should normalize values beginning with a decimal point', () => {
+ it('should normalize values beginning with a decimal point', async () => {
const { node, onChange } = createFormComponent({
schema: {
type: 'number',
@@ -300,29 +273,20 @@ describe('NumberField', () => {
const $input = node.querySelector('input');
- act(() => {
- fireEvent.change($input, {
- target: { value: '.00' },
- });
- });
+ await user.type($input!, '.00');
- sinon.assert.calledWithMatch(
- onChange.lastCall,
- {
- formData: 0,
- },
- 'root',
- );
- expect($input.value).eql('.00');
+ expect(onChange).toHaveBeenLastCalledWith(expect.objectContaining({ formData: 0 }), 'root');
+ const expected = isEmpty(uiSchema) ? 0 : '.00';
+ expect($input).toHaveValue(expected);
});
it('should update input values correctly when formData prop changes', () => {
- const schema = {
+ const schema: RJSFSchema = {
type: 'number',
};
- const { comp, node } = createFormComponent({
- ref: React.createRef(),
+ const { rerender, node } = createFormComponent({
+ ref: createRef(),
schema,
uiSchema,
formData: 2.03,
@@ -330,48 +294,41 @@ describe('NumberField', () => {
const $input = node.querySelector('input');
- expect($input.value).eql('2.03');
+ expect($input).toHaveAttribute('value', '2.03');
- setProps(comp, {
- schema,
- formData: 203,
- });
+ rerender({ schema, formData: 203 });
- expect($input.value).eql('203');
+ expect($input).toHaveAttribute('value', '203');
});
- it('form reset should work for a default value', () => {
- const onChangeSpy = sinon.spy();
- const schema = {
+ it('form reset should work for a default value', async () => {
+ const schema: RJSFSchema = {
type: 'number',
default: 1,
};
- const ref = React.createRef();
+ const ref = createRef