Vulcan/packages/vulcan-forms/test/components.test.js

415 lines
14 KiB
JavaScript
Raw Normal View History

2018-07-24 16:07:42 +02:00
// setup JSDOM server side for testing (necessary for Enzyme to mount)
2018-10-23 15:02:57 +02:00
import 'jsdom-global/register';
import React from 'react';
// TODO: should be loaded from Components instead?
2018-10-23 15:02:57 +02:00
import Form from '../lib/components/Form';
import FormComponent from '../lib/components/FormComponent';
import '../lib/components/FormNestedArray';
import expect from 'expect';
import Enzyme, { mount, shallow } from 'enzyme';
2018-07-24 16:07:42 +02:00
import Adapter from 'enzyme-adapter-react-16';
2018-10-23 15:02:57 +02:00
import { Components } from 'meteor/vulcan:core';
2018-07-24 16:07:42 +02:00
// setup enzyme
// TODO: write a reusable helper and move this to the tests setup
2018-10-23 15:02:57 +02:00
Enzyme.configure({ adapter: new Adapter() });
2018-07-24 16:07:42 +02:00
// we must import all the other components, so that "registerComponent" is called
2018-10-23 15:02:57 +02:00
import '../lib/modules/components';
// and then load them in the app so that <Component.Whatever /> is defined
2018-10-23 15:02:57 +02:00
import { populateComponentsApp, initializeFragments } from 'meteor/vulcan:lib';
// we need registered fragments to be initialized because populateComponentsApp will run
// hocs, like withUpdate, that rely on fragments
2018-10-23 15:02:57 +02:00
initializeFragments();
// actually fills the Components object
2018-10-23 15:02:57 +02:00
populateComponentsApp();
2018-07-24 16:07:42 +02:00
// fixtures
2018-09-12 11:59:00 +09:00
import SimpleSchema from 'simpl-schema';
2018-07-24 16:07:42 +02:00
const addressGroup = {
2018-10-23 15:02:57 +02:00
name: 'addresses',
label: 'Addresses',
order: 10
};
const permissions = {
canRead: ['guests'],
canUpdate: ['quests'],
canCreate: ['guests']
2018-07-24 16:07:42 +02:00
};
const addressSchema = {
2018-10-23 15:02:57 +02:00
street: {
type: String,
optional: true,
...permissions
}
};
2018-10-23 15:02:57 +02:00
// [{street, city,...}, {street, city, ...}]
const arrayOfObjectSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
},
'addresses.$': {
type: new SimpleSchema(addressSchema)
}
};
// example with custom inputs for the children
2018-10-23 15:02:57 +02:00
// ["http://maps/XYZ", "http://maps/foobar"]
const arrayOfUrlSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
},
'addresses.$': {
type: String,
input: 'url'
}
};
2018-10-25 10:23:00 +02:00
const CustomObjectInput = () => 'OBJECT INPUT';
const arrayOfCustomObjectSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
},
'addresses.$': {
type: new SimpleSchema(addressSchema),
input: CustomObjectInput
}
};
// example with a fully custom input for both the array and its children
const ArrayInput = () => 'ARRAY INPUT';
2018-10-25 10:23:00 +02:00
const arrayFullCustomSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions,
input: ArrayInput
},
'addresses.$': {
type: String,
input: 'url'
}
};
2018-10-23 15:02:57 +02:00
// example with a native type
// ["20 rue du Moulin PARIS", "16 rue de la poste PARIS"]
const arrayOfStringSchema = {
addresses: {
type: Array,
group: addressGroup,
...permissions
},
'addresses.$': {
type: String
}
};
// object (not in an array): {street, city}
const objectSchema = {
2018-10-23 15:02:57 +02:00
addresses: {
type: new SimpleSchema(addressSchema),
...permissions
}
};
// without calling SimpleSchema
const bareObjectSchema = {
addresses: {
type: addressSchema,
...permissions
}
2018-07-24 16:07:42 +02:00
};
// stub collection
2018-10-23 15:02:57 +02:00
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
const createDummyCollection = (typeName, schema) =>
createCollection({
collectionName: typeName + 's',
typeName,
schema,
resolvers: getDefaultResolvers(typeName + 's'),
mutations: getDefaultMutations(typeName + 's')
});
2018-10-25 10:23:00 +02:00
const ArrayOfObjects = createDummyCollection('ArrayOfObject', arrayOfObjectSchema);
2018-10-23 15:02:57 +02:00
const Objects = createDummyCollection('Object', objectSchema);
const ArrayOfUrls = createDummyCollection('ArrayOfUrl', arrayOfUrlSchema);
2018-10-25 10:23:00 +02:00
const ArrayOfCustomObjects = createDummyCollection('ArrayOfCustomObject', arrayOfCustomObjectSchema);
const ArrayFullCustom = createDummyCollection('ArrayFullCustom', arrayFullCustomSchema);
const ArrayOfStrings = createDummyCollection('ArrayOfString', arrayOfStringSchema);
2018-07-24 16:07:42 +02:00
const Addresses = createCollection({
2018-10-23 15:02:57 +02:00
collectionName: 'Addresses',
typeName: 'Address',
schema: addressSchema,
resolvers: getDefaultResolvers('Addresses'),
mutations: getDefaultMutations('Addresses')
});
2018-10-23 15:02:57 +02:00
// helpers
// tests
2018-10-23 15:02:57 +02:00
describe('vulcan-forms/components', function() {
const context = {
intl: {
formatMessage: () => '',
formatDate: () => '',
formatTime: () => '',
formatRelative: () => '',
formatNumber: () => '',
formatPlural: () => '',
2018-10-25 10:23:00 +02:00
formatHTMLMessage: () => '',
now: () => ''
2018-10-23 15:02:57 +02:00
}
};
// eslint-disable-next-line no-unused-vars
const mountWithContext = C =>
mount(C, {
context
});
const shallowWithContext = C =>
shallow(C, {
context
});
2018-10-25 10:23:00 +02:00
2018-10-23 15:02:57 +02:00
describe('Form (handle fields computation)', function() {
2018-10-25 10:23:00 +02:00
// getters
const getArrayFormGroup = wrapper => wrapper.find('FormGroup').find({ name: 'addresses' });
const getFields = arrayFormGroup => arrayFormGroup.prop('fields');
2018-10-23 15:02:57 +02:00
describe('basic collection - no nesting', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collection={Addresses} />);
expect(wrapper).toBeDefined();
});
});
describe('nested object (not in array)', function() {
it('shallow render', () => {
const wrapper = shallowWithContext(<Form collection={Objects} />);
expect(wrapper).toBeDefined();
});
it('define one field', () => {
const wrapper = shallowWithContext(<Form collection={Objects} />);
const defaultGroup = wrapper.find('FormGroup').first();
const fields = defaultGroup.prop('fields');
expect(fields).toHaveLength(1); // addresses field
});
2018-10-23 15:02:57 +02:00
const getFormFields = wrapper => {
const defaultGroup = wrapper.find('FormGroup').first();
const fields = defaultGroup.prop('fields');
return fields;
};
const getFirstField = () => {
const wrapper = shallowWithContext(<Form collection={Objects} />);
const fields = getFormFields(wrapper);
return fields[0];
};
it('define the nestedSchema', () => {
const addressField = getFirstField();
expect(addressField.nestedSchema.street).toBeDefined();
});
});
2018-10-25 10:23:00 +02:00
describe('array of objects', function() {
it('shallow render', () => {
const wrapper = shallowWithContext(<Form collection={ArrayOfObjects} />);
expect(wrapper).toBeDefined();
});
it('render a FormGroup for addresses', function() {
const wrapper = shallowWithContext(<Form collection={ArrayOfObjects} />);
const formGroup = wrapper.find('FormGroup').find({ name: 'addresses' });
expect(formGroup).toBeDefined();
expect(formGroup).toHaveLength(1);
});
it('passes down the array child fields', function() {
const wrapper = shallowWithContext(<Form collection={ArrayOfObjects} />);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
expect(arrayField.nestedInput).toBe(true);
expect(arrayField.nestedFields).toHaveLength(Object.keys(addressSchema).length);
});
});
describe('array with custom children inputs (e.g array of url)', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collection={ArrayOfUrls} />);
expect(wrapper).toBeDefined();
});
2018-10-25 10:23:00 +02:00
it('passes down the array item custom input', () => {
const wrapper = shallowWithContext(<Form collection={ArrayOfUrls} />);
2018-10-25 10:23:00 +02:00
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
expect(arrayField.arrayField).toBeDefined();
});
});
describe('array of objects with custom children inputs', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collection={ArrayOfCustomObjects} />);
expect(wrapper).toBeDefined();
});
// TODO: does not work, schema_utils needs an update
it.skip('passes down the custom input', function() {
const wrapper = shallowWithContext(<Form collection={ArrayOfCustomObjects} />);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
expect(arrayField.arrayField).toBeDefined();
});
});
describe('array with a fully custom input (array itself and children)', function() {
it('shallow render', function() {
const wrapper = shallowWithContext(<Form collection={ArrayFullCustom} />);
expect(wrapper).toBeDefined();
});
it('passes down the custom input', function() {
const wrapper = shallowWithContext(<Form collection={ArrayFullCustom} />);
const formGroup = getArrayFormGroup(wrapper);
const fields = getFields(formGroup);
const arrayField = fields[0];
expect(arrayField.arrayField).toBeDefined();
});
});
2018-10-23 15:02:57 +02:00
});
2018-10-23 15:02:57 +02:00
describe('FormComponent (select the components to render and handle state)', function() {
const shallowWithContext = C =>
shallow(C, {
context: {
getDocument: () => {}
}
2018-10-23 15:02:57 +02:00
});
const defaultProps = {
disabled: false,
optional: true,
document: {},
name: 'meetingPlace',
path: 'meetingPlace',
datatype: [{ type: Object }],
layout: 'horizontal',
label: 'Meeting place',
currentValues: {},
formType: 'new',
deletedValues: [],
throwError: () => {},
updateCurrentValues: () => {},
errors: [],
clearFieldErrors: () => {}
};
it('shallow render', function() {
const wrapper = shallowWithContext(<FormComponent {...defaultProps} />);
expect(wrapper).toBeDefined();
});
describe('array of objects', function() {
const props = {
...defaultProps,
datatype: [{ type: Array }],
nestedSchema: {
street: {},
country: {},
zipCode: {}
},
nestedInput: true,
nestedFields: [{}, {}, {}],
currentValues: {}
};
it('render a FormNestedArray', function() {
const wrapper = shallowWithContext(<FormComponent {...props} />);
const formNested = wrapper.find('FormNestedArray');
expect(formNested).toHaveLength(1);
});
});
describe('nested object', function() {
const props = {
...defaultProps,
datatype: [{ type: new SimpleSchema({}) }],
nestedSchema: {
street: {},
country: {},
zipCode: {}
},
nestedInput: true,
nestedFields: [{}, {}, {}],
currentValues: {}
};
it('shallow render', function() {
const wrapper = shallowWithContext(<FormComponent {...props} />);
expect(wrapper).toBeDefined();
});
it('render a FormNestedObject', function() {
const wrapper = shallowWithContext(<FormComponent {...props} />);
const formNested = wrapper.find('FormNestedObject');
expect(formNested).toHaveLength(1);
});
});
describe('array of custom inputs (e.g url)', function() {
it('shallow render', function() {});
});
2018-10-23 15:02:57 +02:00
});
2018-10-23 15:02:57 +02:00
describe('FormNestedArray - Display the input n times', function() {
const defaultProps = {
errors: [],
deletedValues: [],
path: 'foobar',
formComponents: Components
};
it('shallow render', function() {
const wrapper = shallow(<Components.FormNestedArray {...defaultProps} currentValues={{}} />);
expect(wrapper).toBeDefined();
});
2018-10-25 12:14:18 +02:00
// TODO: broken now we use a layout...
it.skip('shows an add button when empty', function() {
const wrapper = mount(<Components.FormNestedArray {...defaultProps} currentValues={{}} />);
2018-10-23 15:02:57 +02:00
const addButton = wrapper.find('IconAdd');
expect(addButton).toHaveLength(1);
});
2018-10-25 12:14:18 +02:00
it.skip('shows 3 items', function() {
const wrapper = mount(<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2, 3]} />);
2018-10-23 15:02:57 +02:00
const nestedItem = wrapper.find('FormNestedItem');
expect(nestedItem).toHaveLength(3);
});
2018-10-25 12:14:18 +02:00
it.skip('pass the correct path and itemIndex to each form', function() {
const wrapper = mount(<Components.FormNestedArray {...defaultProps} currentValues={{}} value={[1, 2]} />);
2018-10-23 15:02:57 +02:00
const nestedItem = wrapper.find('FormNestedItem');
const item0 = nestedItem.at(0);
const item1 = nestedItem.at(1);
expect(item0.prop('itemIndex')).toEqual(0);
expect(item1.prop('itemIndex')).toEqual(1);
expect(item0.prop('path')).toEqual('foobar.0');
expect(item1.prop('path')).toEqual('foobar.1');
});
});
describe('FormNestedObject', function() {
const defaultProps = {
errors: [],
path: 'foobar',
formComponents: Components
};
it('shallow render', function() {
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);
expect(wrapper).toBeDefined();
});
it.skip('render a form for the object', function() {
// eslint-disable-next-line no-unused-vars
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);
expect(false).toBe(true);
});
it('does not show any button', function() {
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);
const button = wrapper.find('BootstrapButton');
expect(button).toHaveLength(0);
});
it('does not show add button', function() {
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);
const addButton = wrapper.find('IconAdd');
expect(addButton).toHaveLength(0);
});
it('does not show remove button', function() {
const wrapper = shallow(<Components.FormNestedObject {...defaultProps} currentValues={{}} />);
const removeButton = wrapper.find('IconRemove');
expect(removeButton).toHaveLength(0);
});
});
});