From 50c880ccfbb681567bd2fb49341ff83e6586aab3 Mon Sep 17 00:00:00 2001 From: brendanjbond Date: Mon, 4 Nov 2024 11:14:37 +0100 Subject: [PATCH 1/2] move path mutation into processOne and out of eachComponent; update fn signatures --- src/process/processOne.ts | 25 +++++++++++++++--- src/utils/formUtil/eachComponent.ts | 32 +++--------------------- src/utils/formUtil/eachComponentAsync.ts | 13 +--------- src/utils/formUtil/index.ts | 3 --- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/src/process/processOne.ts b/src/process/processOne.ts index 941e8ac2..50d2ad83 100644 --- a/src/process/processOne.ts +++ b/src/process/processOne.ts @@ -9,7 +9,7 @@ export function dataValue(component: Component, row: any) { } export async function processOne(context: ProcessorsContext) { - const { processors, component } = context; + const { processors, component, path } = context; // Create a getter for `value` that is always derived from the current data object if (typeof context.value === 'undefined') { Object.defineProperty(context, 'value', { @@ -22,8 +22,18 @@ export async function processOne(context: ProcessorsContext(context: ProcessorsContext(context: ProcessorsContext) { - const { processors, component } = context; + const { processors, component, path } = context; // Create a getter for `value` that is always derived from the current data object if (typeof context.value === 'undefined') { Object.defineProperty(context, 'value', { @@ -49,8 +59,17 @@ export function processOneSync(context: ProcessorsContext { @@ -33,7 +30,7 @@ export function eachComponent( const info = componentInfo(component); let noRecurse = false; // Keep track of parent references. - if (parent && !noComponentChange) { + if (parent) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -58,14 +55,6 @@ export function eachComponent( const compPath = componentPath(component, path); - if (!noComponentChange) { - Object.defineProperty(component, 'path', { - enumerable: false, - writable: true, - value: compPath, - }); - } - if (includeAll || component.tree || !info.layout) { noRecurse = !!fn(component, compPath, components, parent); } @@ -73,27 +62,13 @@ export function eachComponent( if (!noRecurse) { if (info.hasColumns) { component.columns.forEach((column: any) => - eachComponent( - column.components, - fn, - includeAll, - path, - parent ? component : null, - noComponentChange, - ), + eachComponent(column.components, fn, includeAll, path, parent ? component : null), ); } else if (info.hasRows) { component.rows.forEach((row: any) => { if (Array.isArray(row)) { row.forEach((column) => - eachComponent( - column.components, - fn, - includeAll, - path, - parent ? component : null, - noComponentChange, - ), + eachComponent(column.components, fn, includeAll, path, parent ? component : null), ); } }); @@ -104,7 +79,6 @@ export function eachComponent( includeAll, componentFormPath(component, path, compPath), parent ? component : null, - noComponentChange, ); } } diff --git a/src/utils/formUtil/eachComponentAsync.ts b/src/utils/formUtil/eachComponentAsync.ts index deee2143..36985a32 100644 --- a/src/utils/formUtil/eachComponentAsync.ts +++ b/src/utils/formUtil/eachComponentAsync.ts @@ -7,7 +7,6 @@ export async function eachComponentAsync( includeAll = false, path = '', parent?: any, - noComponentChange?: boolean, ) { if (!components) return; for (let i = 0; i < components.length; i++) { @@ -17,7 +16,7 @@ export async function eachComponentAsync( const component = components[i]; const info = componentInfo(component); // Keep track of parent references. - if (parent && !noComponentChange) { + if (parent) { // Ensure we don't create infinite JSON structures. Object.defineProperty(component, 'parent', { enumerable: false, @@ -41,13 +40,6 @@ export async function eachComponentAsync( } const compPath = componentPath(component, path); - if (!noComponentChange) { - Object.defineProperty(component, 'path', { - enumerable: false, - writable: true, - value: compPath, - }); - } if (includeAll || component.tree || !info.layout) { if (await fn(component, compPath, components, parent)) { continue; @@ -61,7 +53,6 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, - noComponentChange, ); } } else if (info.hasRows) { @@ -75,7 +66,6 @@ export async function eachComponentAsync( includeAll, path, parent ? component : null, - noComponentChange, ); } } @@ -87,7 +77,6 @@ export async function eachComponentAsync( includeAll, componentFormPath(component, path, compPath), parent ? component : null, - noComponentChange, ); } } diff --git a/src/utils/formUtil/index.ts b/src/utils/formUtil/index.ts index 318759d7..0350a54f 100644 --- a/src/utils/formUtil/index.ts +++ b/src/utils/formUtil/index.ts @@ -431,9 +431,6 @@ export function getComponent( } }, includeAll, - undefined, - undefined, - true, ); return result; } From 2874d180835eb0cabe26cfa292c5019dc7e73fff Mon Sep 17 00:00:00 2001 From: brendanjbond Date: Mon, 4 Nov 2024 12:53:09 +0100 Subject: [PATCH 2/2] add tests; refactor test organization --- src/utils/__tests__/fixtures/components5.json | 27 - src/utils/__tests__/formUtil.test.ts | 493 +------- .../formUtil/__tests__/eachComponent.test.ts | 1098 +++++++++++++++++ 3 files changed, 1141 insertions(+), 477 deletions(-) delete mode 100644 src/utils/__tests__/fixtures/components5.json create mode 100644 src/utils/formUtil/__tests__/eachComponent.test.ts diff --git a/src/utils/__tests__/fixtures/components5.json b/src/utils/__tests__/fixtures/components5.json deleted file mode 100644 index d33299ab..00000000 --- a/src/utils/__tests__/fixtures/components5.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "label": "HTML", - "tag": "p", - "content": "", - "key": "html", - "type": "htmlelement" - }, - { - "html": "

some text

", - "label": "Content", - "key": "content", - "type": "content" - }, - { - "label": "Text Field", - "key": "textField", - "type": "textfield", - "input": true - }, - { - "label": "Number", - "key": "number", - "type": "number", - "input": true - } -] diff --git a/src/utils/__tests__/formUtil.test.ts b/src/utils/__tests__/formUtil.test.ts index 8d63350d..5e004c94 100644 --- a/src/utils/__tests__/formUtil.test.ts +++ b/src/utils/__tests__/formUtil.test.ts @@ -1,15 +1,13 @@ -import * as fs from 'fs'; +import fs from 'fs'; import get from 'lodash/get'; import { expect } from 'chai'; -import { Component, HasChildComponents, TableComponent } from 'types'; +import { Component } from 'types'; import { getContextualRowData, eachComponentDataAsync, isComponentDataEmpty, - eachComponent, eachComponentData, - isLayoutComponent, findComponent, findComponents, getComponent, @@ -19,19 +17,6 @@ import { getModelType, } from '../formUtil'; -const components = JSON.parse(fs.readFileSync(__dirname + '/fixtures/components.json').toString()); -const components2 = JSON.parse( - fs.readFileSync(__dirname + '/fixtures/components2.json').toString(), -); -const components3 = JSON.parse( - fs.readFileSync(__dirname + '/fixtures/components3.json').toString(), -); -const components4 = JSON.parse( - fs.readFileSync(__dirname + '/fixtures/components4.json').toString(), -); -const components5 = JSON.parse( - fs.readFileSync(__dirname + '/fixtures/components5.json').toString(), -); const writtenNumber = (n: number | null) => { switch (n) { case 1: @@ -55,440 +40,18 @@ const writtenNumber = (n: number | null) => { } }; -describe('formUtil', function () { - describe('eachComponent', function () { - it('should iterate through nested components in the right order', function () { - let n = 1; - eachComponent(components, (component: Component) => { - expect((component as any).order).to.equal(n); - n += 1; - }); - }); - - it('should include layouts components if provided', function () { - let numComps = 0; - let numLayout = 0; - eachComponent( - components, - (component: Component) => { - if (isLayoutComponent(component)) { - numLayout++; - } else { - numComps++; - } - }, - true, - ); - expect(numLayout).to.be.equal(3); - expect(numComps).to.be.equal(8); - }); - - it('Should provide the paths to all of the components', function () { - const paths = [ - 'one', - 'parent1', - 'two', - 'parent2', - 'three', - '', - 'four', - 'five', - 'six', - 'seven', - 'eight', - ]; - const testPaths: string[] = []; - eachComponent( - components, - (component: Component, path: string) => { - testPaths.push(path); - }, - true, - ); - expect(paths).to.deep.equal(testPaths); - }); - - it('Should iterate over each component given a flat components array', function () { - const components = [ - { - type: 'textfield', - key: 'textField', - input: true, - }, - { - type: 'textarea', - key: 'textArea', - input: true, - }, - ]; - const rowResults: Map = new Map(); - eachComponent(components, (component: Component, path: string) => { - rowResults.set(path, component); - }); - expect(rowResults.size).to.equal(2); - expect(rowResults.get('textField')).to.deep.equal({ - type: 'textfield', - key: 'textField', - input: true, - }); - expect(rowResults.get('textArea')).to.deep.equal({ - type: 'textarea', - key: 'textArea', - input: true, - }); - }); - - it('Should iterate over each component with correct pathing given a container component', function () { - const components = [ - { - type: 'textfield', - key: 'textField', - input: true, - }, - { - type: 'container', - key: 'container', - input: true, - components: [ - { - type: 'textfield', - key: 'nestedTextField', - input: true, - }, - { - type: 'textarea', - key: 'nestedTextArea', - input: true, - }, - ], - }, - ]; - const rowResults: Map = new Map(); - eachComponent( - components, - (component: Component, path: string) => { - rowResults.set(path, component); - }, - true, - ); - expect(rowResults.size).to.equal(4); - expect(rowResults.get('textField')).to.deep.equal({ - type: 'textfield', - key: 'textField', - input: true, - }); - expect(rowResults.get('container')).to.deep.equal({ - type: 'container', - key: 'container', - input: true, - components: [ - { - type: 'textfield', - key: 'nestedTextField', - input: true, - }, - { - type: 'textarea', - key: 'nestedTextArea', - input: true, - }, - ], - }); - expect(rowResults.get('container.nestedTextField')).to.deep.equal({ - type: 'textfield', - key: 'nestedTextField', - input: true, - }); - expect(rowResults.get('container.nestedTextArea')).to.deep.equal({ - type: 'textarea', - key: 'nestedTextArea', - input: true, - }); - }); - - it('Should iterate over each component with correct pathing given a datagrid component', function () { - const components = [ - { - type: 'textfield', - key: 'textField', - input: true, - }, - { - type: 'datagrid', - key: 'dataGrid', - input: true, - components: [ - { - type: 'textfield', - key: 'nestedTextField', - input: true, - }, - { - type: 'textarea', - key: 'nestedTextArea', - input: true, - }, - ], - }, - ]; - const rowResults: Map = new Map(); - eachComponent( - components, - (component: Component, path: string) => { - rowResults.set(path, component); - }, - true, - ); - expect(rowResults.size).to.equal(4); - expect(rowResults.get('textField')).to.deep.equal({ - type: 'textfield', - key: 'textField', - input: true, - }); - expect(rowResults.get('dataGrid')).to.deep.equal({ - type: 'datagrid', - key: 'dataGrid', - input: true, - components: [ - { - type: 'textfield', - key: 'nestedTextField', - input: true, - }, - { - type: 'textarea', - key: 'nestedTextArea', - input: true, - }, - ], - }); - expect(rowResults.get('dataGrid.nestedTextField')).to.deep.equal({ - type: 'textfield', - key: 'nestedTextField', - input: true, - }); - expect(rowResults.get('dataGrid.nestedTextArea')).to.deep.equal({ - type: 'textarea', - key: 'nestedTextArea', - input: true, - }); - }); - - it("Should iterate over each component with correct pathing given a datagrid's child components", function () { - const components = [ - { - type: 'datagrid', - key: 'dataGrid', - input: true, - components: [ - { - type: 'textfield', - key: 'nestedTextField', - input: true, - }, - { - type: 'textarea', - key: 'nestedTextArea', - input: true, - }, - ], - }, - ]; - const rowResults: Map = new Map(); - eachComponent( - components[0].components, - (component: Component, path: string) => { - rowResults.set(path, component); - }, - true, - 'dataGrid', - ); - expect(rowResults.size).to.equal(2); - expect(rowResults.get('dataGrid.nestedTextField')).to.deep.equal({ - type: 'textfield', - key: 'nestedTextField', - input: true, - }); - expect(rowResults.get('dataGrid.nestedTextArea')).to.deep.equal({ - type: 'textarea', - key: 'nestedTextArea', - input: true, - }); - }); - - describe('findComponent', function () { - it('should find correct component in nested structure', function () { - findComponent(components4, 'four', null, (component: Component) => { - expect(component.label).to.equal('4'); - }); - }); - - it('should find correct component in flat structure', function () { - findComponent(components4, 'one', null, (component: Component) => { - expect(component.label).to.equal('1'); - }); - }); - }); - - it('Should be able to find all textfield components', function () { - const comps = findComponents(components, { type: 'textfield' }); - expect(comps.length).to.equal(6); - }); - - it('Should be able to find components with special properties.', function () { - const comps = findComponents(components3, { 'properties.path': 'a' }); - expect(comps.length).to.equal(4); - expect(comps[0].key).to.equal('b'); - expect(comps[1].key).to.equal('e'); - expect(comps[2].key).to.equal('j'); - expect(comps[3].key).to.equal('m'); - }); - - it('Should be able to generate paths based on component types', function () { - const paths = [ - 'a', - 'b', - 'c', - 'd', - 'f', - 'f.g', - 'f.h', - 'f.i', - 'e', - 'j', - 'k', - 'k.n', - 'k.n.o', - 'k.n.p', - 'k.n.q', - 'k.m', - 'k.l', - 'r', - 'submit', - 'tagpad', - 'tagpad.a', - ]; - const testPaths: string[] = []; - eachComponent( - components2, - (component: Component, path: string) => { - testPaths.push(path); - }, - true, - ); - expect(paths).to.deep.equal(testPaths); - }); - - it('Should still provide the correct paths when it is not recursive', function () { - const paths = [ - 'a', - 'd', - 'f', - 'f.g', - 'f.h', - 'f.i', - 'e', - 'j', - 'k', - 'k.n', - 'k.n.o', - 'k.n.p', - 'k.n.q', - 'k.m', - 'k.l', - 'r', - 'submit', - 'tagpad', - 'tagpad.a', - ]; - const testPaths: string[] = []; - eachComponent(components2, (component: Component, path: string) => { - testPaths.push(path); - }); - expect(paths).to.deep.equal(testPaths); - }); - - it('should be able to block recursion', function () { - let numComps = 0; - let numLayout = 0; - eachComponent( - components, - (component: Component) => { - if (isLayoutComponent(component)) { - numLayout++; - } else { - numComps++; - } - - if (component.type === 'table') { - let numInTable = 0; - const tableComponent: TableComponent = component as TableComponent; - tableComponent.rows.forEach((row: Component[]) => { - row.forEach((comp: Component) => { - eachComponent((comp as HasChildComponents).components, () => { - numInTable++; - }); - }); - }); - expect(numInTable).to.be.equal(4); - return true; - } - }, - true, - ); - expect(numLayout).to.be.equal(3); - expect(numComps).to.be.equal(4); - }); - - it('should not include `htmlelement` components when `includeAll` is not provided', function () { - let htmlComponentsAmount = 0; - eachComponent(components5, (component: Component) => { - if (component.type === 'htmlelement') { - htmlComponentsAmount++; - } - }); - expect(htmlComponentsAmount).to.be.equal(0); - }); - - it('should include `htmlelement` components when `includeAll` is provided', function () { - let htmlComponentsAmount = 0; - eachComponent( - components5, - (component: Component) => { - if (component.type === 'htmlelement') { - htmlComponentsAmount++; - } - }, - true, - ); - expect(htmlComponentsAmount).to.be.equal(1); - }); - - it('should not include `content` components when `includeAll` is not provided', function () { - let contentComponentsAmount = 0; - eachComponent(components5, (component: Component) => { - if (component.type === 'content') { - contentComponentsAmount++; - } - }); - expect(contentComponentsAmount).to.be.equal(0); - }); - - it('should include `content` components when `includeAll` is provided', function () { - let contentComponentsAmount = 0; - eachComponent( - components5, - (component: Component) => { - if (component.type === 'content') { - contentComponentsAmount++; - } - }, - true, - ); - expect(contentComponentsAmount).to.be.equal(1); - }); - }); +const components = JSON.parse(fs.readFileSync(__dirname + '/fixtures/components.json').toString()); +const components2 = JSON.parse( + fs.readFileSync(__dirname + '/fixtures/components2.json').toString(), +); +const components3 = JSON.parse( + fs.readFileSync(__dirname + '/fixtures/components3.json').toString(), +); +const components4 = JSON.parse( + fs.readFileSync(__dirname + '/fixtures/components4.json').toString(), +); +describe('formUtil', function () { describe('getComponent', function () { it('should return the correct components', function () { for (let n = 1; n <= 8; n += 1) { @@ -2189,4 +1752,34 @@ describe('formUtil', function () { expect(actual).to.equal(expected); }); }); + + describe('findComponent', function () { + it('should find correct component in nested structure', function () { + findComponent(components2, 'four', null, (component: Component) => { + expect(component.label).to.equal('4'); + }); + }); + + it('should find correct component in flat structure', function () { + findComponent(components4, 'one', null, (component: Component) => { + expect(component.label).to.equal('1'); + }); + }); + }); + + describe('findComponents', function () { + it('Should be able to find all textfield components', function () { + const comps = findComponents(components, { type: 'textfield' }); + expect(comps.length).to.equal(6); + }); + + it('Should be able to find components with special properties.', function () { + const comps = findComponents(components3, { 'properties.path': 'a' }); + expect(comps.length).to.equal(4); + expect(comps[0].key).to.equal('b'); + expect(comps[1].key).to.equal('e'); + expect(comps[2].key).to.equal('j'); + expect(comps[3].key).to.equal('m'); + }); + }); }); diff --git a/src/utils/formUtil/__tests__/eachComponent.test.ts b/src/utils/formUtil/__tests__/eachComponent.test.ts new file mode 100644 index 00000000..94d9beaa --- /dev/null +++ b/src/utils/formUtil/__tests__/eachComponent.test.ts @@ -0,0 +1,1098 @@ +import { expect } from 'chai'; + +import { eachComponent } from '../eachComponent'; +import { isLayoutComponent } from '../index'; + +import { Component, TableComponent, HasChildComponents } from 'types'; + +describe('eachComponent', function () { + const components = [ + { + type: 'textfield', + key: 'one', + order: 1, + input: true, + }, + { + input: false, + key: 'parent1', + components: [ + { + type: 'textfield', + key: 'two', + order: 2, + }, + { + input: false, + key: 'parent2', + columns: [ + { + components: [ + { + type: 'textfield', + key: 'three', + order: 3, + }, + ], + }, + { + components: [ + { + rows: [ + [ + { + components: [ + { + key: 'four', + order: 4, + type: 'textfield', + }, + ], + }, + { + components: [ + { + key: 'five', + order: 5, + type: 'textfield', + }, + ], + }, + ], + [ + { + components: [ + { + key: 'six', + order: 6, + type: 'textfield', + }, + ], + }, + { + components: [ + { + key: 'seven', + order: 7, + type: 'textarea', + rows: 3, + }, + ], + }, + ], + ], + type: 'table', + }, + ], + }, + ], + type: 'columns', + }, + ], + type: 'well', + }, + { + key: 'eight', + order: 8, + type: 'button', + input: true, + }, + ]; + const components2 = [ + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'a', + label: 'A', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + lockKey: true, + key: 'b', + conditional: { + eq: '', + when: null, + show: null, + }, + type: 'fieldset', + components: [ + { + lockKey: true, + key: 'c', + conditional: { + eq: '', + when: null, + show: null, + }, + type: 'columns', + columns: [ + { + components: [ + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'd', + label: 'D', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + conditional: { + eq: '', + when: null, + show: null, + }, + type: 'container', + persistent: true, + protected: false, + key: 'f', + label: 'F', + tableView: true, + components: [ + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'g', + label: 'G', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'h', + label: 'H', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'i', + label: 'I', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + ], + tree: true, + input: true, + }, + ], + }, + { + components: [ + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'e', + label: 'E', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + ], + }, + ], + input: false, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'j', + label: 'J', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + ], + legend: 'B', + tableView: true, + input: false, + }, + { + conditional: { + eq: '', + when: null, + show: null, + }, + type: 'datagrid', + persistent: true, + protected: false, + key: 'k', + label: 'K', + tableView: true, + components: [ + { + conditional: { + eq: '', + when: null, + show: null, + }, + hideLabel: true, + type: 'container', + persistent: true, + protected: false, + key: 'n', + label: 'N', + tableView: true, + components: [ + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'o', + label: 'O', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'p', + label: 'P', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'q', + label: 'Q', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + ], + tree: true, + input: true, + }, + { + hideLabel: true, + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'm', + label: 'M', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + hideLabel: true, + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'l', + label: 'L', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + ], + tree: true, + input: true, + }, + { + type: 'textfield', + conditional: { + eq: '', + when: null, + show: null, + }, + validate: { + customPrivate: false, + custom: '', + pattern: '', + maxLength: '', + minLength: '', + required: false, + }, + persistent: true, + unique: false, + protected: false, + defaultValue: '', + multiple: false, + suffix: '', + prefix: '', + placeholder: '', + key: 'r', + label: 'R', + inputMask: '', + inputType: 'text', + tableView: true, + input: true, + }, + { + type: 'button', + theme: 'primary', + disableOnInvalid: true, + action: 'submit', + block: false, + rightIcon: '', + leftIcon: '', + size: 'md', + key: 'submit', + tableView: false, + label: 'Submit', + input: true, + }, + { + label: 'Tagpad', + tableView: false, + key: 'tagpad', + type: 'tagpad', + input: true, + components: [ + { + label: 'Text Field', + tableView: true, + key: 'a', + type: 'textfield', + input: true, + }, + ], + }, + ]; + const components3 = [ + { + label: 'HTML', + tag: 'p', + content: '', + key: 'html', + type: 'htmlelement', + input: false, + }, + { + html: '

some text

', + label: 'Content', + key: 'content', + type: 'content', + input: false, + }, + { + label: 'Text Field', + key: 'textField', + type: 'textfield', + input: true, + }, + { + label: 'Number', + key: 'number', + type: 'number', + input: true, + }, + ]; + + it('should iterate through nested components in the right order', function () { + let n = 1; + eachComponent(components, (component: Component) => { + expect((component as any).order).to.equal(n); + n += 1; + }); + }); + + it('should include layouts components if provided', function () { + let numComps = 0; + let numLayout = 0; + eachComponent( + components, + (component: Component) => { + if (isLayoutComponent(component)) { + numLayout++; + } else { + numComps++; + } + }, + true, + ); + expect(numLayout).to.be.equal(3); + expect(numComps).to.be.equal(8); + }); + + it('Should provide the paths to all of the components', function () { + const paths = [ + 'one', + 'parent1', + 'two', + 'parent2', + 'three', + '', + 'four', + 'five', + 'six', + 'seven', + 'eight', + ]; + const testPaths: string[] = []; + eachComponent( + components, + (component: Component, path: string) => { + testPaths.push(path); + }, + true, + ); + expect(paths).to.deep.equal(testPaths); + }); + + it('Should iterate over each component given a flat components array', function () { + const components = [ + { + type: 'textfield', + key: 'textField', + input: true, + }, + { + type: 'textarea', + key: 'textArea', + input: true, + }, + ]; + const rowResults: Map = new Map(); + eachComponent(components, (component: Component, path: string) => { + rowResults.set(path, component); + }); + expect(rowResults.size).to.equal(2); + expect(rowResults.get('textField')).to.deep.equal({ + type: 'textfield', + key: 'textField', + input: true, + }); + expect(rowResults.get('textArea')).to.deep.equal({ + type: 'textarea', + key: 'textArea', + input: true, + }); + }); + + it('Should iterate over each component with correct pathing given a container component', function () { + const components = [ + { + type: 'textfield', + key: 'textField', + input: true, + }, + { + type: 'container', + key: 'container', + input: true, + components: [ + { + type: 'textfield', + key: 'nestedTextField', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + input: true, + }, + ], + }, + ]; + const rowResults: Map = new Map(); + eachComponent( + components, + (component: Component, path: string) => { + rowResults.set(path, component); + }, + true, + ); + expect(rowResults.size).to.equal(4); + expect(rowResults.get('textField')).to.deep.equal({ + type: 'textfield', + key: 'textField', + input: true, + }); + expect(rowResults.get('container')).to.deep.equal({ + type: 'container', + key: 'container', + input: true, + components: [ + { + type: 'textfield', + key: 'nestedTextField', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + input: true, + }, + ], + }); + expect(rowResults.get('container.nestedTextField')).to.deep.equal({ + type: 'textfield', + key: 'nestedTextField', + input: true, + }); + expect(rowResults.get('container.nestedTextArea')).to.deep.equal({ + type: 'textarea', + key: 'nestedTextArea', + input: true, + }); + }); + + it('Should iterate over each component with correct pathing given a datagrid component', function () { + const components = [ + { + type: 'textfield', + key: 'textField', + input: true, + }, + { + type: 'datagrid', + key: 'dataGrid', + input: true, + components: [ + { + type: 'textfield', + key: 'nestedTextField', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + input: true, + }, + ], + }, + ]; + const rowResults: Map = new Map(); + eachComponent( + components, + (component: Component, path: string) => { + rowResults.set(path, component); + }, + true, + ); + expect(rowResults.size).to.equal(4); + expect(rowResults.get('textField')).to.deep.equal({ + type: 'textfield', + key: 'textField', + input: true, + }); + expect(rowResults.get('dataGrid')).to.deep.equal({ + type: 'datagrid', + key: 'dataGrid', + input: true, + components: [ + { + type: 'textfield', + key: 'nestedTextField', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + input: true, + }, + ], + }); + expect(rowResults.get('dataGrid.nestedTextField')).to.deep.equal({ + type: 'textfield', + key: 'nestedTextField', + input: true, + }); + expect(rowResults.get('dataGrid.nestedTextArea')).to.deep.equal({ + type: 'textarea', + key: 'nestedTextArea', + input: true, + }); + }); + + it("Should iterate over each component with correct pathing given a datagrid's child components", function () { + const components = [ + { + type: 'datagrid', + key: 'dataGrid', + input: true, + components: [ + { + type: 'textfield', + key: 'nestedTextField', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + input: true, + }, + ], + }, + ]; + const rowResults: Map = new Map(); + eachComponent( + components[0].components, + (component: Component, path: string) => { + rowResults.set(path, component); + }, + true, + 'dataGrid', + ); + expect(rowResults.size).to.equal(2); + expect(rowResults.get('dataGrid.nestedTextField')).to.deep.equal({ + type: 'textfield', + key: 'nestedTextField', + input: true, + }); + expect(rowResults.get('dataGrid.nestedTextArea')).to.deep.equal({ + type: 'textarea', + key: 'nestedTextArea', + input: true, + }); + }); + + it('Should be able to generate paths based on component types', function () { + const paths = [ + 'a', + 'b', + 'c', + 'd', + 'f', + 'f.g', + 'f.h', + 'f.i', + 'e', + 'j', + 'k', + 'k.n', + 'k.n.o', + 'k.n.p', + 'k.n.q', + 'k.m', + 'k.l', + 'r', + 'submit', + 'tagpad', + 'tagpad.a', + ]; + const testPaths: string[] = []; + eachComponent( + components2, + (component: Component, path: string) => { + testPaths.push(path); + }, + true, + ); + expect(paths).to.deep.equal(testPaths); + }); + + it('Should still provide the correct paths when it is not recursive', function () { + const paths = [ + 'a', + 'd', + 'f', + 'f.g', + 'f.h', + 'f.i', + 'e', + 'j', + 'k', + 'k.n', + 'k.n.o', + 'k.n.p', + 'k.n.q', + 'k.m', + 'k.l', + 'r', + 'submit', + 'tagpad', + 'tagpad.a', + ]; + const testPaths: string[] = []; + eachComponent(components2, (component: Component, path: string) => { + testPaths.push(path); + }); + expect(paths).to.deep.equal(testPaths); + }); + + it('should be able to block recursion', function () { + let numComps = 0; + let numLayout = 0; + eachComponent( + components, + (component: Component) => { + if (isLayoutComponent(component)) { + numLayout++; + } else { + numComps++; + } + + if (component.type === 'table') { + let numInTable = 0; + const tableComponent: TableComponent = component as TableComponent; + tableComponent.rows.forEach((row: Component[]) => { + row.forEach((comp: Component) => { + eachComponent((comp as HasChildComponents).components, () => { + numInTable++; + }); + }); + }); + expect(numInTable).to.be.equal(4); + return true; + } + }, + true, + ); + expect(numLayout).to.be.equal(3); + expect(numComps).to.be.equal(4); + }); + + it('should not include `htmlelement` components when `includeAll` is not provided', function () { + let htmlComponentsAmount = 0; + eachComponent(components3, (component: Component) => { + if (component.type === 'htmlelement') { + htmlComponentsAmount++; + } + }); + expect(htmlComponentsAmount).to.be.equal(0); + }); + + it('should include `htmlelement` components when `includeAll` is provided', function () { + let htmlComponentsAmount = 0; + eachComponent( + components3, + (component: Component) => { + if (component.type === 'htmlelement') { + htmlComponentsAmount++; + } + }, + true, + ); + expect(htmlComponentsAmount).to.be.equal(1); + }); + + it('should not include `content` components when `includeAll` is not provided', function () { + let contentComponentsAmount = 0; + eachComponent(components3, (component: Component) => { + if (component.type === 'content') { + contentComponentsAmount++; + } + }); + expect(contentComponentsAmount).to.be.equal(0); + }); + + it('should include `content` components when `includeAll` is provided', function () { + let contentComponentsAmount = 0; + eachComponent( + components3, + (component: Component) => { + if (component.type === 'content') { + contentComponentsAmount++; + } + }, + true, + ); + expect(contentComponentsAmount).to.be.equal(1); + }); + + it('should not mutate the path property if contained in component', function () { + const components = [ + { + type: 'textfield', + key: 'textField', + input: true, + path: 'doNotMutate', + }, + { + type: 'container', + key: 'container', + input: true, + path: 'doNotMutate', + components: [ + { + type: 'textfield', + key: 'nestedTextField', + path: 'doNotMutate', + input: true, + }, + { + type: 'textarea', + key: 'nestedTextArea', + path: 'doNotMutate', + input: true, + }, + ], + }, + ]; + eachComponent( + components, + (component: Component, path: string) => { + if (component.key === 'textField') { + expect(component.path).to.equal('doNotMutate'); + expect(path).to.equal('textField'); + } + if (component.key === 'container') { + expect(component.path).to.equal('doNotMutate'); + expect(path).to.equal('container'); + } + if (component.key === 'nestedTextField') { + expect(component.path).to.equal('doNotMutate'); + expect(path).to.equal('container.nestedTextField'); + } + if (component.key === 'nestedTextArea') { + expect(component.path).to.equal('doNotMutate'); + expect(path).to.equal('container.nestedTextArea'); + } + }, + true, + ); + }); +});