diff --git a/ui/spec/shared/reducers/helpers/fieldSpec.js b/ui/spec/shared/reducers/helpers/fieldSpec.js index b801aa953c..3883ea07bc 100644 --- a/ui/spec/shared/reducers/helpers/fieldSpec.js +++ b/ui/spec/shared/reducers/helpers/fieldSpec.js @@ -1,10 +1,77 @@ -import {numFunctions} from 'shared/reducers/helpers/field' +import _ from 'lodash' +import { + fieldWalk, + removeField, + getFieldsDeep, + fieldNamesDeep, +} from 'shared/reducers/helpers/fields' -describe('Formatting helpers', () => { - describe('formatBytes', () => { - it('returns null when passed a falsey value', () => { - const actual = numFunctions(null) - expect(actual).to.equal(0) +describe('field helpers', () => { + it('can walk all fields and get all names', () => { + const fields = [ + { + name: 'fn1', + type: 'func', + args: [{name: 'f1', type: 'func', args: [{name: 'f2', type: 'field'}]}], + }, + {name: 'fn1', type: 'func', args: [{name: 'f2', type: 'field'}]}, + {name: 'fn2', type: 'func', args: [{name: 'f2', type: 'field'}]}, + ] + const actual = fieldWalk(fields, f => _.get(f, 'name')) + expect(actual).to.deep.equal(['fn1', 'f1', 'f2', 'fn1', 'f2', 'fn2', 'f2']) + }) + + it('can return all unique fields for type field', () => { + const fields = [ + { + name: 'fn1', + type: 'func', + args: [{name: 'f1', type: 'func', args: [{name: 'f2', type: 'field'}]}], + }, + {name: 'fn1', type: 'func', args: [{name: 'f2', type: 'field'}]}, + {name: 'fn2', type: 'func', args: [{name: 'f2', type: 'field'}]}, + ] + const actual = getFieldsDeep(fields) + expect(actual).to.deep.equal([{name: 'f2', type: 'field'}]) + }) + + it('can return all unique field names for type field', () => { + const fields = [ + { + name: 'fn1', + type: 'func', + args: [{name: 'f1', type: 'func', args: [{name: 'f2', type: 'field'}]}], + }, + {name: 'fn1', type: 'func', args: [{name: 'f2', type: 'field'}]}, + {name: 'fn2', type: 'func', args: [{name: 'f2', type: 'field'}]}, + ] + const actual = fieldNamesDeep(fields) + expect(actual).to.deep.equal(['f2']) + }) + + describe('removeField', () => { + it('can remove fields at any level of the tree', () => { + const fields = [ + { + name: 'fn1', + type: 'func', + args: [ + {name: 'f1', type: 'func', args: [{name: 'f2', type: 'field'}]}, + ], + }, + {name: 'fn2', type: 'func', args: [{name: 'f2', type: 'field'}]}, + {name: 'fn3', type: 'func', args: [{name: 'f3', type: 'field'}]}, + ] + const actual = removeField('f2', fields) + expect(actual).to.deep.equal([ + {name: 'fn3', type: 'func', args: [{name: 'f3', type: 'field'}]}, + ]) + }) + + it('can remove fields from a flat field list', () => { + const fields = [{name: 'f1', type: 'field'}, {name: 'f2', type: 'field'}] + const actual = removeField('f2', fields) + expect(actual).to.deep.equal([{name: 'f1', type: 'field'}]) }) }) }) diff --git a/ui/src/shared/reducers/helpers/fields.js b/ui/src/shared/reducers/helpers/fields.js index ecc6877225..2021c6d5a8 100644 --- a/ui/src/shared/reducers/helpers/fields.js +++ b/ui/src/shared/reducers/helpers/fields.js @@ -1,8 +1,15 @@ import _ from 'lodash' -// fieldWalk traverses fields rescursively into args -export const fieldWalk = (fields, fn) => - fields.each(f => _.concat(fn(f), fieldWalk(_.get(f, 'args', []), fn))) +// fieldWalk traverses fields rescursively into args mapping fn on every +// field +export const fieldWalk = (fields, fn, acc = []) => + _.compact( + _.flattenDeep( + fields.reduce((a, f) => { + return [...a, fn(f), fieldWalk(_.get(f, 'args', []), fn, acc)] + }, acc) + ) + ) // functions returns all top-level fields with type export const ofType = (fields, type) => @@ -20,11 +27,14 @@ export const functionNames = fields => functions(fields).map(f => f.name) // getFields returns all of the top-level fields of type field export const getFields = fields => ofType(fields, 'field') -export const fieldsDeep = fields => - _.uniqBy(fieldWalk(fields, f => getFields(f)), 'name') +export const getFieldsDeep = fields => + _.uniqBy( + fieldWalk(fields, f => (_.get(f, 'type') === 'field' ? f : null)), + 'name' + ) export const fieldNamesDeep = fields => - fieldsDeep(fields).map(f => _.get(f, 'name')) + getFieldsDeep(fields).map(f => _.get(f, 'name')) // firstFieldName returns the name of the first of type field export const firstFieldName = fields => _.head(fieldNamesDeep(fields)) @@ -43,17 +53,18 @@ export const everyField = fields => everyOfType(fields, 'field') export const everyFunction = fields => everyOfType(fields, 'func') // removeField will remove the field or function from the field list with the -// given fieldName +// given fieldName. Preconditions: only type field OR only type func export const removeField = (fieldName, fields) => { if (everyField(fields)) { return fields.filter(f => f.name !== fieldName) } return fields.reduce((acc, f) => { - const has = fieldNamesDeep(f.args).some(n => n.name === fieldName) + const has = fieldNamesDeep(f.args).some(n => n === fieldName) if (has) { - return [...acc, f] + return acc } - return acc + + return [...acc, f] }, []) }