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