// setup JSDOM server side for testing (necessary for Enzyme to mount) import 'jsdom-global/register'; import React from 'react'; // TODO: should be loaded from Components instead? import Form from '../lib/components/Form'; import FormComponent from '../lib/components/FormComponent'; import '../lib/components/FormNestedArray'; import expect from 'expect'; import { mount, shallow } from 'enzyme'; import { Components } from 'meteor/vulcan:core'; import { initComponentTest } from 'meteor/vulcan:test'; // we must import all the other components, so that "registerComponent" is called import '../lib/modules/components'; // setup Vulcan (load components, initialize fragments) initComponentTest(); // fixtures import SimpleSchema from 'simpl-schema'; const addressGroup = { name: 'addresses', label: 'Addresses', order: 10 }; const permissions = { canRead: ['guests'], canUpdate: ['quests'], canCreate: ['guests'] }; // just 1 input for state testing const fooSchema = { foo: { type: String, ...permissions } }; // const addressSchema = { street: { type: String, optional: true, ...permissions } }; // [{street, city,...}, {street, city, ...}] const arrayOfObjectSchema = { addresses: { type: Array, group: addressGroup, ...permissions }, 'addresses.$': { type: new SimpleSchema(addressSchema) } }; // example with custom inputs for the children // ["http://maps/XYZ", "http://maps/foobar"] const arrayOfUrlSchema = { addresses: { type: Array, group: addressGroup, ...permissions }, 'addresses.$': { type: String, input: 'url' } }; // example with array and custom input 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'; const arrayFullCustomSchema = { addresses: { type: Array, group: addressGroup, ...permissions, input: ArrayInput }, 'addresses.$': { type: String, input: 'url' } }; // 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 = { addresses: { type: new SimpleSchema(addressSchema), ...permissions } }; // without calling SimpleSchema const bareObjectSchema = { addresses: { type: addressSchema, ...permissions } }; // stub collection 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') }); const Foos = createDummyCollection('Foo', fooSchema); const ArrayOfObjects = createDummyCollection('ArrayOfObject', arrayOfObjectSchema); const Objects = createDummyCollection('Object', objectSchema); const ArrayOfUrls = createDummyCollection('ArrayOfUrl', arrayOfUrlSchema); const ArrayOfCustomObjects = createDummyCollection('ArrayOfCustomObject', arrayOfCustomObjectSchema); const ArrayFullCustom = createDummyCollection('ArrayFullCustom', arrayFullCustomSchema); const ArrayOfStrings = createDummyCollection('ArrayOfString', arrayOfStringSchema); const Addresses = createCollection({ collectionName: 'Addresses', typeName: 'Address', schema: addressSchema, resolvers: getDefaultResolvers('Addresses'), mutations: getDefaultMutations('Addresses') }); // helpers // tests describe('vulcan-forms/components', function() { const context = { intl: { formatMessage: () => '', formatDate: () => '', formatTime: () => '', formatRelative: () => '', formatNumber: () => '', formatPlural: () => '', formatHTMLMessage: () => '', now: () => '' } }; // eslint-disable-next-line no-unused-vars const mountWithContext = C => mount(C, { context }); const shallowWithContext = C => shallow(C, { context }); describe('Form collectionName="" (handle fields computation)', function() { // since some props are now handled by HOC we need to provide them manually const defaultProps = { collectionName: '', typeName: '' }; describe('Form generation', function() { // getters const getArrayFormGroup = wrapper => wrapper.find('FormGroup').find({ name: 'addresses' }); const getFields = arrayFormGroup => arrayFormGroup.prop('fields'); describe('basic collection - no nesting', function() { it('shallow render', function() { const wrapper = shallowWithContext(
); expect(wrapper).toBeDefined(); }); }); describe('nested object (not in array)', function() { it('shallow render', () => { const wrapper = shallowWithContext(); expect(wrapper).toBeDefined(); }); it('define one field', () => { const wrapper = shallowWithContext(); const defaultGroup = wrapper.find('FormGroup').first(); const fields = defaultGroup.prop('fields'); expect(fields).toHaveLength(1); // addresses field }); const getFormFields = wrapper => { const defaultGroup = wrapper.find('FormGroup').first(); const fields = defaultGroup.prop('fields'); return fields; }; const getFirstField = () => { const wrapper = shallowWithContext(); const fields = getFormFields(wrapper); return fields[0]; }; it('define the nestedSchema', () => { const addressField = getFirstField(); expect(addressField.nestedSchema.street).toBeDefined(); }); }); describe('array of objects', function() { it('shallow render', () => { const wrapper = shallowWithContext(); expect(wrapper).toBeDefined(); }); it('render a FormGroup for addresses', function() { const wrapper = shallowWithContext(); 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(); 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(); expect(wrapper).toBeDefined(); }); it('passes down the array item custom input', () => { const wrapper = shallowWithContext(); 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(); expect(wrapper).toBeDefined(); }); // TODO: does not work, schema_utils needs an update it.skip('passes down the custom input', function() { const wrapper = shallowWithContext(); 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(); expect(wrapper).toBeDefined(); }); it('passes down the custom input', function() { const wrapper = shallowWithContext(); const formGroup = getArrayFormGroup(wrapper); const fields = getFields(formGroup); const arrayField = fields[0]; expect(arrayField.arrayField).toBeDefined(); }); }); }); describe('Form state management', function() { // TODO: the change callback is triggerd but `foo` becomes null instead of "bar // so it's added to the deletedValues and not changedValues it.skip('store typed value', function() { const wrapper = mountWithContext(); //console.log(wrapper.state()); wrapper .find('input') .first() .simulate('change', { target:{value:'bar'} }); console.log(wrapper.find('input').first().html()) console.log(wrapper.state()); expect(wrapper.state().currentValues).toEqual({foo:'bar'}) }); it('reset state when relevant props change', function() { const wrapper = shallowWithContext(); wrapper.setState({ currentValues: { foo: 'bar' } }) expect(wrapper.state('currentValues')).toEqual({foo:'bar'}) wrapper.setProps({ collectionName: 'Bars' }) expect(wrapper.state('currentValues')).toEqual({}) }); it('does not reset state when external prop change', function(){ //const prefilledProps = { bar: 'foo' } // TODO const changeCallback= () => 'CHANGE' const wrapper = shallowWithContext(); wrapper.setState({ currentValues: { foo: 'bar' } }) expect(wrapper.state('currentValues')).toEqual({foo:'bar'}) const newChangeCallback = () => 'NEW' wrapper.setProps({ changeCallback: newChangeCallback }) expect(wrapper.state('currentValues')).toEqual({ foo:'bar'}) }) }); }); describe('FormComponent (select the components to render and handle state)', function() { const shallowWithContext = C => shallow(C, { context: { getDocument: () => {} } }); 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(