diff --git a/packages/form-js-editor/test/spec/FormEditor.spec.js b/packages/form-js-editor/test/spec/FormEditor.spec.js index fef519e7f..df5c76251 100644 --- a/packages/form-js-editor/test/spec/FormEditor.spec.js +++ b/packages/form-js-editor/test/spec/FormEditor.spec.js @@ -73,7 +73,7 @@ describe('FormEditor', function() { }); // then - expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(17); + expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(20); }); @@ -114,7 +114,7 @@ describe('FormEditor', function() { }); // then - expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(17); + expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(20); }); @@ -137,7 +137,7 @@ describe('FormEditor', function() { container.querySelector('.fjs-container').classList.add('fjs-no-theme'); // then - expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(17); + expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(20); }); @@ -243,7 +243,7 @@ describe('FormEditor', function() { await formEditor.importSchema(schema); // then - expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(17); + expect(formEditor.get('formFieldRegistry').getAll()).to.have.length(20); }); @@ -980,7 +980,7 @@ describe('FormEditor', function() { // assume const formFieldRegistry = formEditor.get('formFieldRegistry'); - expect(formFieldRegistry.getAll()).to.have.length(17); + expect(formFieldRegistry.getAll()).to.have.length(20); // when startDragging(container); @@ -988,7 +988,7 @@ describe('FormEditor', function() { endDragging(container); // then - expect(formFieldRegistry.getAll()).to.have.length(18); + expect(formFieldRegistry.getAll()).to.have.length(21); const selection = formEditor.get('selection'); diff --git a/packages/form-js-editor/test/spec/core/FieldFactory.spec.js b/packages/form-js-editor/test/spec/core/FieldFactory.spec.js index fdc44cfa9..ac53fa0f3 100644 --- a/packages/form-js-editor/test/spec/core/FieldFactory.spec.js +++ b/packages/form-js-editor/test/spec/core/FieldFactory.spec.js @@ -220,7 +220,20 @@ describe('core/FieldFactory', function() { })); - it('should throw if ID already assigned', inject(function(fieldFactory) { + it('should not assign key (path)', inject(function(fieldFactory) { + + // when + const field = fieldFactory.create({ + key: 'foo.bar', + type: 'textfield' + }, false); + + // then + expect(field.key).to.equal('foo.bar'); + })); + + + it('should throw if binding path is already claimed (simple key)', inject(function(fieldFactory) { // given fieldFactory.create({ @@ -238,6 +251,25 @@ describe('core/FieldFactory', function() { expect(create).to.throw('binding path \'foo\' is already claimed'); })); + + it('should throw if binding path is already claimed (path key)', inject(function(fieldFactory) { + + // given + fieldFactory.create({ + key: 'foo.bar', + type: 'textfield' + }, false); + + // when + const create = () => fieldFactory.create({ + key: 'foo.bar', + type: 'textfield' + }, false); + + // then + expect(create).to.throw('binding path \'foo.bar\' is already claimed'); + })); + }); }); diff --git a/packages/form-js-editor/test/spec/features/modeling/Modeling.spec.js b/packages/form-js-editor/test/spec/features/modeling/Modeling.spec.js index feb102f8a..13abdf859 100644 --- a/packages/form-js-editor/test/spec/features/modeling/Modeling.spec.js +++ b/packages/form-js-editor/test/spec/features/modeling/Modeling.spec.js @@ -605,74 +605,342 @@ describe('features/modeling', function() { }); + + describe('different parent', function() { + + describe('into inner', function() { + + const sourceIndex = 0, + targetIndex = 0; + + let sourceFormFieldIds, + targetFormFieldIds, + formFieldsLength, + sourceParent, + targetParent; + + beforeEach(inject(function(formFieldRegistry, modeling) { + + // given + formFieldsLength = formFieldRegistry.getAll().length; + + sourceParent = formFieldRegistry.get('Form_1'); + targetParent = formFieldRegistry.get('Group_1'); + + const formField = sourceParent.components[ sourceIndex ]; + + sourceFormFieldIds = sourceParent.components.map(({ id }) => id); + targetFormFieldIds = targetParent.components.map(({ id }) => id); + + // when + modeling.moveFormField( + formField, + sourceParent, + targetParent, + sourceIndex, + targetIndex + ); + })); + + + it('', inject(function(formFieldRegistry) { + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Form_1'); + const targetParent = formFieldRegistry.get('Group_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds.slice(1)); + expect(targetParent.components.map(({ id }) => id)).to.eql([ + sourceFormFieldIds[ 0 ], + ...targetFormFieldIds + ]); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + // when + commandStack.undo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Form_1'); + const targetParent = formFieldRegistry.get('Group_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds); + expect(targetParent.components.map(({ id }) => id)).to.eql(targetFormFieldIds); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + // when + commandStack.undo(); + commandStack.redo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Form_1'); + const targetParent = formFieldRegistry.get('Group_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds.slice(1)); + expect(targetParent.components.map(({ id }) => id)).to.eql([ + sourceFormFieldIds[ 0 ], + ...targetFormFieldIds + ]); + + })); + + }); + + + describe('into outer', function() { + + const sourceIndex = 0, + targetIndex = 0; + + let sourceFormFieldIds, + targetFormFieldIds, + formFieldsLength, + sourceParent, + targetParent; + + beforeEach(inject(function(formFieldRegistry, modeling) { + + // given + formFieldsLength = formFieldRegistry.getAll().length; + + sourceParent = formFieldRegistry.get('Group_1'); + targetParent = formFieldRegistry.get('Form_1'); + + const formField = sourceParent.components[ sourceIndex ]; + + sourceFormFieldIds = sourceParent.components.map(({ id }) => id); + targetFormFieldIds = targetParent.components.map(({ id }) => id); + + // when + modeling.moveFormField( + formField, + sourceParent, + targetParent, + sourceIndex, + targetIndex + ); + })); + + + it('', inject(function(formFieldRegistry) { + + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Group_1'); + const targetParent = formFieldRegistry.get('Form_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds.slice(1)); + expect(targetParent.components.map(({ id }) => id)).to.eql([ + sourceFormFieldIds[ 0 ], + ...targetFormFieldIds + ]); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + // when + commandStack.undo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Group_1'); + const targetParent = formFieldRegistry.get('Form_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds); + expect(targetParent.components.map(({ id }) => id)).to.eql(targetFormFieldIds); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + + // when + commandStack.undo(); + commandStack.redo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const sourceParent = formFieldRegistry.get('Group_1'); + const targetParent = formFieldRegistry.get('Form_1'); + + expect(sourceParent.components.map(({ id }) => id)).to.eql(sourceFormFieldIds.slice(1)); + expect(targetParent.components.map(({ id }) => id)).to.eql([ + sourceFormFieldIds[ 0 ], + ...targetFormFieldIds + ]); + })); + + }); + + }); + }); describe('#removeFormField', function() { - const sourceIndex = 0; - let formFieldIds, - formFieldsLength; + describe('simple field', function() { - beforeEach(inject(function(formFieldRegistry, modeling) { + const sourceIndex = 0; - // given - formFieldsLength = formFieldRegistry.getAll().length; + let formFieldIds, + formFieldsLength; - const parent = formFieldRegistry.get('Form_1'); + beforeEach(inject(function(formFieldRegistry, modeling) { - const formField = parent.components[ sourceIndex ]; + // given + formFieldsLength = formFieldRegistry.getAll().length; - formFieldIds = parent.components.map(({ id }) => id); + const parent = formFieldRegistry.get('Form_1'); - // when - modeling.removeFormField( - formField, - parent, - sourceIndex - ); - })); + const formField = parent.components[ sourceIndex ]; + formFieldIds = parent.components.map(({ id }) => id); - it('', inject(function(formFieldRegistry) { + // when + modeling.removeFormField( + formField, + parent, + sourceIndex + ); + })); - // then - expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - 1); - const parent = formFieldRegistry.get('Form_1'); + it('', inject(function(formFieldRegistry) { - expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds.slice(1)); - })); + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - 1); + const parent = formFieldRegistry.get('Form_1'); - it('', inject(function(commandStack, formFieldRegistry) { + expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds.slice(1)); + })); - // when - commandStack.undo(); - // then - expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + it('', inject(function(commandStack, formFieldRegistry) { - const parent = formFieldRegistry.get('Form_1'); + // when + commandStack.undo(); - expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds); - })); + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + const parent = formFieldRegistry.get('Form_1'); - it('', inject(function(commandStack, formFieldRegistry) { + expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds); + })); - // when - commandStack.undo(); - commandStack.redo(); - // then - expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - 1); + it('', inject(function(commandStack, formFieldRegistry) { - const parent = formFieldRegistry.get('Form_1'); + // when + commandStack.undo(); + commandStack.redo(); - expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds.slice(1)); - })); + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - 1); + + const parent = formFieldRegistry.get('Form_1'); + + expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds.slice(1)); + })); + + }); + + + describe('group', function() { + + const sourceIndex = 1; + + let formFieldIds, + groupFieldCount, + formFieldsLength; + + beforeEach(inject(function(formFieldRegistry, modeling) { + + // given + formFieldsLength = formFieldRegistry.getAll().length; + + const parent = formFieldRegistry.get('Form_1'); + + const group = formFieldRegistry.get('Group_1'); + + groupFieldCount = group.components.length + 1; + + formFieldIds = parent.components.map(({ id }) => id); + + // when + modeling.removeFormField( + group, + parent, + sourceIndex + ); + })); + + + it('', inject(function(formFieldRegistry) { + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - groupFieldCount); + + const parent = formFieldRegistry.get('Form_1'); + + expect(parent.components.map(({ id }) => id)).to.eql([ + formFieldIds[ 0 ], + ...formFieldIds.slice(2) + ]); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + // when + commandStack.undo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength); + + const parent = formFieldRegistry.get('Form_1'); + + expect(parent.components.map(({ id }) => id)).to.eql(formFieldIds); + })); + + + it('', inject(function(commandStack, formFieldRegistry) { + + // when + commandStack.undo(); + commandStack.redo(); + + // then + expect(formFieldRegistry.getAll()).to.have.length(formFieldsLength - groupFieldCount); + + const parent = formFieldRegistry.get('Form_1'); + + expect(parent.components.map(({ id }) => id)).to.eql([ + formFieldIds[ 0 ], + ...formFieldIds.slice(2) + ]); + })); + + }); }); diff --git a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js index 0729513a9..3b543325f 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/PropertiesPanel.spec.js @@ -2400,6 +2400,45 @@ describe('properties panel', function() { }); + describe('group', function() { + + it('entries', function() { + + // given + const field = schema.components.find(({ type }) => type === 'group'); + + const result = createPropertiesPanel({ + container, + field + }); + + // then + expectGroups(result.container, [ + 'General', + 'Condition', + 'Layout', + 'Custom properties' + ]); + + expectGroupEntries(result.container, 'General', [ + 'Group label', + 'Path', + 'Show outline' + ]); + + expectGroupEntries(result.container, 'Condition', [ + 'Hide if' + ]); + + expectGroupEntries(result.container, 'Layout', [ + 'Columns' + ]); + + }); + + }); + + describe('textfield', function() { it('entries', function() { @@ -2667,6 +2706,37 @@ describe('properties panel', function() { expect(error).to.exist; }); + + it('should not allow numerical key segments', function() { + + // given + const editFieldSpy = spy(); + + const field = schema.components.find(({ key }) => key === 'creditor'); + + createPropertiesPanel({ + container, + editField: editFieldSpy, + field + }); + + // assume + const input = screen.getByLabelText('Key', { selector: '#bio-properties-panel-key' }); + + expect(input.value).to.equal('creditor'); + + // when + fireEvent.input(input, { target: { value: 'credi.0.tor' } }); + + // then + expect(editFieldSpy).not.to.have.been.called; + + const error = screen.getByText('Must not contain numerical path segments.'); + + expect(error).to.exist; + + }); + }); }); @@ -3301,6 +3371,37 @@ describe('properties panel', function() { expect(error).to.exist; }); + + it('should not allow numerical key segments', function() { + + // given + const editFieldSpy = spy(); + + const field = schema.components.find(({ key }) => key === 'amount'); + + createPropertiesPanel({ + container, + editField: editFieldSpy, + field + }); + + // assume + const input = screen.getByLabelText('Key', { selector: '#bio-properties-panel-key' }); + + expect(input.value).to.equal('amount'); + + // when + fireEvent.input(input, { target: { value: 'amou.0.nt' } }); + + // then + expect(editFieldSpy).not.to.have.been.called; + + const error = screen.getByText('Must not contain numerical path segments.'); + + expect(error).to.exist; + + }); + }); }); diff --git a/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js b/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js index 43afb0f49..efb92c124 100644 --- a/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js +++ b/packages/form-js-editor/test/spec/features/properties-panel/groups/GeneralGroup.spec.js @@ -129,6 +129,21 @@ describe('GeneralGroup', function() { }); + it('should render for group', function() { + + // given + const field = { type: 'group' }; + + // when + const { container } = renderGeneralGroup({ field }); + + // then + const labelInput = findFeelers('label', container); + + expect(labelInput).to.exist; + }); + + it('should render feel editor', function() { // given @@ -1226,7 +1241,9 @@ function _getService(type, options = {}) { if (type === 'pathRegistry') { return { getValuePath: options.getValuePath || ((field) => [ field.key ]), - canClaimPath: options.canClaimPath || (() => true) + canClaimPath: options.canClaimPath || (() => true), + claimPath: options.claimPath || (() => {}), + unclaimPath: options.unclaimPath || (() => {}) }; } } diff --git a/packages/form-js-editor/test/spec/form.json b/packages/form-js-editor/test/spec/form.json index a4707fd36..067eda3d3 100644 --- a/packages/form-js-editor/test/spec/form.json +++ b/packages/form-js-editor/test/spec/form.json @@ -7,6 +7,27 @@ "type": "text", "text": "# Invoice\n\nLorem _ipsum_ __dolor__ `sit`.\n\nA list of BPMN symbols:\n* Start Event\n* Task\nLearn more about [forms](https://bpmn.io).\n\nThis [malicious link](javascript:throw onerror=alert,'some string',123,'haha') __should not work__." }, + { + "id": "Group_1", + "type": "group", + "label": "Supplementary Information", + "path": "invoiceDetails", + "showOutline": true, + "components": [ + { + "id": "GroupTextfield_1", + "type": "textfield", + "key": "supplementaryInfo1", + "label": "Field 1" + }, + { + "id": "GroupTextfield_2", + "type": "textfield", + "key": "supplementaryInfo2", + "label": "Field 2" + } + ] + }, { "id": "Textfield_1", "key": "creditor", diff --git a/packages/form-js-editor/test/spec/import/Importer.spec.js b/packages/form-js-editor/test/spec/import/Importer.spec.js index b24fe9901..29f110e07 100644 --- a/packages/form-js-editor/test/spec/import/Importer.spec.js +++ b/packages/form-js-editor/test/spec/import/Importer.spec.js @@ -30,7 +30,7 @@ describe('Importer', function() { // then expect(warnings).to.be.empty; - expect(formFieldRegistry.getAll()).to.have.length(17); + expect(formFieldRegistry.getAll()).to.have.length(20); })); @@ -40,7 +40,7 @@ describe('Importer', function() { await formEditor.importSchema(schema); // assume - expect(formFieldRegistry.getAll()).to.have.length(17); + expect(formFieldRegistry.getAll()).to.have.length(20); // when const result = await formEditor.importSchema(other); diff --git a/packages/form-js-viewer/src/core/FieldFactory.js b/packages/form-js-viewer/src/core/FieldFactory.js index 217c81fe0..5dff10283 100644 --- a/packages/form-js-viewer/src/core/FieldFactory.js +++ b/packages/form-js-viewer/src/core/FieldFactory.js @@ -44,7 +44,7 @@ export default class FieldFactory { const parentPath = parent && this._pathRegistry.getValuePath(parent) || []; - if (config.keyed && key && !this._pathRegistry.canClaimPath([ ...parentPath, key ], true)) { + if (config.keyed && key && !this._pathRegistry.canClaimPath([ ...parentPath, ...key.split('.') ], true)) { throw new Error(`binding path '${ [ ...parentPath, key ].join('.') }' is already claimed`); } diff --git a/packages/form-js-viewer/src/core/PathRegistry.js b/packages/form-js-viewer/src/core/PathRegistry.js index 2791dbac5..441d174e3 100644 --- a/packages/form-js-viewer/src/core/PathRegistry.js +++ b/packages/form-js-viewer/src/core/PathRegistry.js @@ -35,7 +35,7 @@ export default class PathRegistry { claimPath(path, closed = false) { if (!this.canClaimPath(path, closed)) { - throw new Error(`cannot claim path ${ path.join('.') }`); + throw new Error(`cannot claim path '${ path.join('.') }'`); } let node = { children: this._dataPaths }; @@ -67,7 +67,7 @@ export default class PathRegistry { for (const segment of path) { const child = _getNextSegment(node, segment); if (!child) { - throw new Error(`no open path found for ${ path }`); + throw new Error(`no open path found for '${ path.join('.') }'`); } node = child; } @@ -95,11 +95,11 @@ export default class PathRegistry { const formFieldConfig = this._formFields.get(field.type).config; if (formFieldConfig.keyed) { - const callResult = fn({ field, isClosed: true, context: context }); + const callResult = fn({ field, isClosed: true, context }); return result && callResult; } else if (formFieldConfig.routed) { - const callResult = fn({ field, isClosed: false, context: context }); + const callResult = fn({ field, isClosed: false, context }); result = result && callResult; } diff --git a/packages/form-js-viewer/test/spec/core/PathRegistry.spec.js b/packages/form-js-viewer/test/spec/core/PathRegistry.spec.js new file mode 100644 index 000000000..1ea8c6af0 --- /dev/null +++ b/packages/form-js-viewer/test/spec/core/PathRegistry.spec.js @@ -0,0 +1,349 @@ +import { + bootstrapForm, + getForm, + inject +} from 'test/TestHelper'; + +describe('PathRegistry', function() { + + let localPathRegistry, + localFormFieldRegistry; + + beforeEach(bootstrapForm()); + + beforeEach(inject(function(pathRegistry, formFieldRegistry) { + localPathRegistry = pathRegistry; + localFormFieldRegistry = formFieldRegistry; + })); + + afterEach(function() { + getForm().destroy(); + }); + + describe('#canClaimPath', function() { + + it('should claim path when available', function() { + expect(localPathRegistry.canClaimPath([ 'foo', 'bar' ])).to.equal(true); + }); + + + it('should NOT allow path claim when unavailable (closed over open)', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + expect(localPathRegistry.canClaimPath([ 'foo', 'bar' ], true)).to.equal(false); + }); + + + it('should NOT allow path claim when unavailable (closed over closed)', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], true); + expect(localPathRegistry.canClaimPath([ 'foo', 'bar' ], true)).to.equal(false); + }); + + }); + + + describe('#claimPath', function() { + + it('should NOT throw error when two same open paths are claimed', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar' ], false)).to.not.throw(); + }); + + + it('should throw error when two same closed paths are claimed', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], true); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar' ], true)).to.throw('cannot claim path \'foo.bar\''); + }); + + + it('should throw error if a closed path would clash with an open path', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + expect(() => localPathRegistry.claimPath([ 'foo' ], true)).to.throw('cannot claim path \'foo\''); + }); + + + it('should throw an error if a closed path would clash with an open path (2)', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar' ], true)).to.throw('cannot claim path \'foo.bar\''); + }); + + + it('should NOT throw an error if a closed path is a subpath of an open path', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar', 'baz' ], true)).to.not.throw(); + }); + + + it('should claim, unclaim and claim again without issue (closed)', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + localPathRegistry.unclaimPath([ 'foo', 'bar' ]); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar' ], false)).to.not.throw(); + }); + + + it('should claim, unclaim and claim again without issue (open)', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], true); + localPathRegistry.unclaimPath([ 'foo', 'bar' ]); + expect(() => localPathRegistry.claimPath([ 'foo', 'bar' ], true)).to.not.throw(); + }); + + }); + + + describe('#unclaimPath', function() { + + it('should throw error when no path found', function() { + expect(() => localPathRegistry.unclaimPath([ 'foo', 'bar' ])).to.throw('no open path found for \'foo.bar\''); + }); + + }); + + + describe('#executeRecursivelyOnFields', function() { + + it('should execute function recursively', function() { + const field = { + type: 'group', + components: [ + { type: 'textfield' }, + { type: 'textfield' }, + { type: 'image' } + ] + }; + + const spyFn = sinon.spy(); + localPathRegistry.executeRecursivelyOnFields(field, spyFn); + + expect(spyFn).to.have.been.calledThrice; + }); + + + it('should not execute at all for non-bound fields', function() { + const field = { + type: 'default', + components: [ + { type: 'image' }, + { type: 'image' }, + { type: 'image' } + ] + }; + + const spyFn = sinon.spy(); + + localPathRegistry.executeRecursivelyOnFields(field, spyFn); + + expect(spyFn).to.not.have.been.called; + }); + + + it('should pass context down recursively', function() { + const field = { + type: 'group', + components: [ + { type: 'textfield' }, + { type: 'textfield' }, + { type: 'image' } + ] + }; + + const spyFn = sinon.spy(); + localPathRegistry.executeRecursivelyOnFields(field, spyFn, { foo: 'bar' }); + + expect(spyFn).to.have.been.calledThrice; + expect(spyFn.firstCall.args[0].context).to.eql({ foo: 'bar' }); + expect(spyFn.secondCall.args[0].context).to.eql({ foo: 'bar' }); + }); + + + it('should pass isClosed state to function', function() { + const field = { + type: 'group', + components: [ + { type: 'textfield' }, + { type: 'textfield' }, + { type: 'image' } + ] + }; + + const spyFn = sinon.spy(); + localPathRegistry.executeRecursivelyOnFields(field, spyFn); + + expect(spyFn).to.have.been.calledThrice; + expect(spyFn.firstCall.args[0].isClosed).to.be.false; + expect(spyFn.secondCall.args[0].isClosed).to.be.true; + }); + + + }); + + + describe('#getValuePath', function() { + + it('should get simple value path', function() { + const field = { + id: 'FooID', + type: 'textfield', + key: 'foo' + }; + + const result = localPathRegistry.getValuePath(field); + expect(result).to.eql([ 'foo' ]); + }); + + + it('should get value path with replacement', function() { + const field = { + id: 'FooID', + type: 'textfield', + key: 'foo' + }; + + const options = { replacements: { FooID: 'bar' } }; + + const result = localPathRegistry.getValuePath(field, options); + expect(result).to.eql([ 'bar' ]); + }); + + + it('should get value path with parent', function() { + + const parent = { + id: 'BarID', + type: 'group', + path: 'bar' + }; + + const child = { + id: 'FooID', + type: 'textfield', + key: 'foo', + _parent: 'BarID' + }; + + localFormFieldRegistry.add(parent); + const result = localPathRegistry.getValuePath(child); + expect(result).to.eql([ 'bar', 'foo' ]); + }); + + + it('should get value path with empty parent', function() { + + const parent = { + id: 'BarID', + type: 'group', + path: '' + }; + + const child = { + id: 'FooID', + type: 'textfield', + key: 'foo', + _parent: 'BarID' + }; + + localFormFieldRegistry.add(parent); + const result = localPathRegistry.getValuePath(child); + expect(result).to.eql([ 'foo' ]); + }); + + + it('should get value path with parent and replacement', function() { + + const parent = { + id: 'BarID', + type: 'group', + path: 'bar' + }; + + const child = { + id: 'FooID', + type: 'textfield', + key: 'foo', + _parent: 'BarID' + }; + + localFormFieldRegistry.add(parent); + const options = { replacements: { BarID: 'baz' } }; + + const result = localPathRegistry.getValuePath(child, options); + expect(result).to.eql([ 'baz', 'foo' ]); + }); + + + it('should get value path with multiple levels of parents', function() { + + const grandparent = { + id: 'GrandparentID', + type: 'group', + path: 'grandparent' + }; + + const parent = { + id: 'ParentID', + type: 'group', + path: 'parent', + _parent: 'GrandparentID' + }; + + const child = { + id: 'ChildID', + type: 'textfield', + key: 'child', + _parent: 'ParentID' + }; + + localFormFieldRegistry.add(grandparent); + localFormFieldRegistry.add(parent); + + const result = localPathRegistry.getValuePath(child); + expect(result).to.eql([ 'grandparent', 'parent', 'child' ]); + }); + + + it('should get value path with multiple levels of parents with dot accessed paths', function() { + + const grandparent = { + id: 'GrandparentID', + type: 'group', + path: 'grandparent.a' + }; + + const parent = { + id: 'ParentID', + type: 'group', + path: 'parent.b', + _parent: 'GrandparentID' + }; + + const child = { + id: 'ChildID', + type: 'textfield', + key: 'child.c', + _parent: 'ParentID' + }; + + localFormFieldRegistry.add(grandparent); + localFormFieldRegistry.add(parent); + + const result = localPathRegistry.getValuePath(child); + + expect(result).to.eql([ 'grandparent', 'a', 'parent', 'b', 'child', 'c' ]); + + }); + + }); + + + describe('#clear', function() { + + it('should clear all paths', function() { + localPathRegistry.claimPath([ 'foo', 'bar' ], false); + localPathRegistry.claimPath([ 'foo', 'bar', 'baz' ], true); + localPathRegistry.claimPath([ 'foo', 'bar', 'qux' ], true); + localPathRegistry.clear(); + + expect(localPathRegistry._dataPaths).to.be.empty; + }); + + }); + +});