From 6726e93e3e55aeea39008e2c147d478b9a85eee5 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 13:39:35 -0400 Subject: [PATCH 01/17] Use actual VueX modules in our Validator JS test. --- .../js/tests/FieldConditionsValidator.test.js | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 86988174862..13966919c01 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -3,37 +3,56 @@ import Vuex from 'vuex'; import ValidatesFieldConditions from '../components/field-conditions/ValidatorMixin.js'; Vue.use(Vuex); + const Store = new Vuex.Store({ - state: { - publish: { - base: { - values: {}, - hiddenFields: {}, - } - }, + modules: { statamic: { - conditions: {}, + namespaced: true, + state: { + conditions: {}, + }, + mutations: { + setCondition(state, payload) { + state.conditions[payload.name] = payload.condition; + }, + }, }, + publish: { + namespaced: true, + modules: { + base: { + namespaced: true, + state: { + values: {}, + hiddenFields: {}, + revealerFields: [], + // statamic: { + // conditions: {}, + // }, + }, + mutations: { + setValues(state, values) { + state.values = values; + }, + setHiddenField(state, field) { + state.hiddenFields[field.dottedKey] = { + hidden: field.hidden, + omitValue: field.omitValue, + }; + }, + setRevealerField(state, field) { + state.revealerFields.push(field.dottedKey); + }, + } + } + } + } }, - mutations: { - setValues(state, values) { - state.publish.base.values = values; - }, - setHiddenField(state, field) { - state.publish.base.hiddenFields[field.dottedKey] = { - hidden: field.hidden, - omitValue: field.omitValue, - }; - }, - setCondition(state, payload) { - state.statamic.conditions[payload.name] = payload.condition; - }, - } }); const Statamic = { $conditions: { - add: (name, condition) => Store.commit('setCondition', {name, condition}) + add: (name, condition) => Store.commit('statamic/setCondition', {name, condition}) } }; @@ -49,10 +68,10 @@ const Fields = new Vue({ methods: { setValues(values) { this.values = values; - Store.commit('setValues', values); + Store.commit('publish/base/setValues', values); }, setStoreValues(values) { - Store.commit('setValues', values); + Store.commit('publish/base/setValues', values); } } }); @@ -398,13 +417,13 @@ test('it can externally force hide a field before validator conditions are evalu expect(Fields.showField({handle: 'some_field'})).toBe(true); expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(true); - Store.commit('setHiddenField', { + Store.commit('publish/base/setHiddenField', { dottedKey: 'last_name', hidden: 'force', omitValue: true, }); - Store.commit('setHiddenField', { + Store.commit('publish/base/setHiddenField', { dottedKey: 'some_field', hidden: 'force', omitValue: true, From 96ece4bfdc894179538ae35375d827427743f389 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 13:51:14 -0400 Subject: [PATCH 02/17] Remove old test idea. --- resources/js/tests/FieldConditionsValidator.test.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 13966919c01..0b29bab2c6e 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -432,16 +432,3 @@ test('it can externally force hide a field before validator conditions are evalu expect(Fields.showField({handle: 'some_field'})).toBe(false); expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(false); }); - -// TODO: Implement wildcards using asterisks? Is this useful? -// test('it can run conditions on nested data using wildcards', () => { -// Fields.setValues({ -// related_posts: [ -// {title: 'Learning Laravel', slug: 'learning-laravel'}, -// {title: 'Learning Vue', slug: 'learning-vue'}, -// ] -// }); - -// expect(showFieldIf({'related_posts.*.title': 'Learning Vue'})).toBe(true); -// expect(showFieldIf({'related_posts.*.title': 'Learning Vim'})).toBe(false); -// }); From e146aff514a3d928eafc3cb805fb8519175017d9 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 13:54:33 -0400 Subject: [PATCH 03/17] Add test coverage for our revealer-hidden field handling. --- .../js/tests/FieldConditionsValidator.test.js | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 0b29bab2c6e..623e80b68e1 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -72,7 +72,13 @@ const Fields = new Vue({ }, setStoreValues(values) { Store.commit('publish/base/setValues', values); - } + }, + setHiddenField(payload) { + Store.commit('publish/base/setHiddenField', payload); + }, + setRevealerField(dottedKey) { + Store.commit('publish/base/setRevealerField', {dottedKey}); + }, } }); @@ -417,13 +423,13 @@ test('it can externally force hide a field before validator conditions are evalu expect(Fields.showField({handle: 'some_field'})).toBe(true); expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(true); - Store.commit('publish/base/setHiddenField', { + Fields.setHiddenField({ dottedKey: 'last_name', hidden: 'force', omitValue: true, }); - Store.commit('publish/base/setHiddenField', { + Fields.setHiddenField({ dottedKey: 'some_field', hidden: 'force', omitValue: true, @@ -432,3 +438,35 @@ test('it can externally force hide a field before validator conditions are evalu expect(Fields.showField({handle: 'some_field'})).toBe(false); expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(false); }); + +test('it omits hidden fields by default', async () => { + Fields.setValues({ + is_online_event: false, + event_venue: false, + }); + + // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + Fields.showField({handle: 'is_online_event'}); + Fields.showField({handle: 'event_venue', if: {is_online_event: true}}); + await Vue.nextTick(); + + expect(Store.state.publish.base.hiddenFields.is_online_event.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(true); +}); + +test('it does not omit revealer hidden fields', async () => { + Fields.setValues({ + show_more_info: false, + event_venue: false, + }); + + Fields.setRevealerField('show_more_info'); + + // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + Fields.showField({handle: 'show_more_info'}); + Fields.showField({handle: 'event_venue', if: {show_more_info: true}}); + await Vue.nextTick(); + + expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(false); +}); From 8d61c8feca8887a64aaefa2fae81183e5026708a Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 14:55:58 -0400 Subject: [PATCH 04/17] Improve testing around `visibility` state. --- .../js/tests/FieldConditionsValidator.test.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 623e80b68e1..b43f31d6534 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -426,20 +426,30 @@ test('it can externally force hide a field before validator conditions are evalu Fields.setHiddenField({ dottedKey: 'last_name', hidden: 'force', - omitValue: true, + omitValue: false, }); Fields.setHiddenField({ dottedKey: 'some_field', hidden: 'force', - omitValue: true, + omitValue: false, }); expect(Fields.showField({handle: 'some_field'})).toBe(false); expect(Fields.showField({handle: 'last_name', if: {first_name: 'Jesse'}})).toBe(false); }); -test('it omits hidden fields by default', async () => { +test('it force hides fields with hidden visibility config', async () => { + // Triggering these showField() checks and waiting a tick should set their `hidden` state in the store... + expect(Fields.showField({handle: 'first_name'})).toBe(true); + expect(Fields.showField({handle: 'last_name', visibility: 'hidden'})).toBe(false); + await Vue.nextTick(); + + expect(Store.state.publish.base.hiddenFields.first_name.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.last_name.hidden).toBe('force'); +}); + +test('it tells omitter to omit hidden fields by default', async () => { Fields.setValues({ is_online_event: false, event_venue: false, @@ -454,7 +464,7 @@ test('it omits hidden fields by default', async () => { expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(true); }); -test('it does not omit revealer hidden fields', async () => { +test('it tells omitter not omit revealer hidden fields', async () => { Fields.setValues({ show_more_info: false, event_venue: false, From 264ce7da10e2e14964f36b44b92f48ddee13c646 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 15:30:57 -0400 Subject: [PATCH 05/17] Rename var for clarity. --- resources/js/components/data-list/HasHiddenFields.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/components/data-list/HasHiddenFields.js b/resources/js/components/data-list/HasHiddenFields.js index 85bc4c74815..b4e3b83670f 100644 --- a/resources/js/components/data-list/HasHiddenFields.js +++ b/resources/js/components/data-list/HasHiddenFields.js @@ -13,12 +13,12 @@ export default { }, visibleValues() { - let hiddenFields = _.chain(this.hiddenFields) + let omittableFields = _.chain(this.hiddenFields) .pick(field => field.hidden && field.omitValue) .keys() .value(); - return new HiddenValuesOmitter(this.values, this.jsonSubmittingFields).omit(hiddenFields); + return new HiddenValuesOmitter(this.values, this.jsonSubmittingFields).omit(omittableFields); }, } From 71fca4097e4759ea4d370c3a5e3f9cb33dea8711 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 16:36:55 -0400 Subject: [PATCH 06/17] Allow `ValidatorMixin` to completely handle hidden logic. --- resources/js/components/data-list/HasHiddenFields.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/data-list/HasHiddenFields.js b/resources/js/components/data-list/HasHiddenFields.js index b4e3b83670f..7ce09bff2b5 100644 --- a/resources/js/components/data-list/HasHiddenFields.js +++ b/resources/js/components/data-list/HasHiddenFields.js @@ -14,7 +14,7 @@ export default { visibleValues() { let omittableFields = _.chain(this.hiddenFields) - .pick(field => field.hidden && field.omitValue) + .pick(field => field.omitValue) .keys() .value(); From da66c723f00588c5eecb8fe7b42a352b3a631948 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 16:40:01 -0400 Subject: [PATCH 07/17] Omit boolean values submitted by revealer fields themselves. --- .../components/field-conditions/Validator.js | 2 +- .../field-conditions/ValidatorMixin.js | 4 +-- .../js/tests/FieldConditionsValidator.test.js | 33 ++++++++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/resources/js/components/field-conditions/Validator.js b/resources/js/components/field-conditions/Validator.js index ce839ea1130..860223184e8 100644 --- a/resources/js/components/field-conditions/Validator.js +++ b/resources/js/components/field-conditions/Validator.js @@ -283,7 +283,7 @@ export default class { return checkedFields; } - hasRevealerCondition(dottedPrefix) { + isHiddenByRevealer(dottedPrefix) { if (! this.store || ! this.storeName) { return false; } diff --git a/resources/js/components/field-conditions/ValidatorMixin.js b/resources/js/components/field-conditions/ValidatorMixin.js index b325a0f5cde..faaf14cc1b3 100644 --- a/resources/js/components/field-conditions/ValidatorMixin.js +++ b/resources/js/components/field-conditions/ValidatorMixin.js @@ -30,12 +30,10 @@ export default { // Ensure DOM is updated to ensure all revealers are properly loaded and tracked before committing to store. this.$nextTick(() => { - let hasRevealerCondition = validator.hasRevealerCondition(dottedPrefix); - this.$store.commit(`publish/${this.storeName}/setHiddenField`, { dottedKey: dottedFieldPath, hidden: ! passes, - omitValue: (! passes) && (! hasRevealerCondition), + omitValue: field.type === 'revealer' || (! passes && ! validator.isHiddenByRevealer(dottedPrefix)), }); }); diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index b43f31d6534..2594595fd8c 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -447,6 +447,8 @@ test('it force hides fields with hidden visibility config', async () => { expect(Store.state.publish.base.hiddenFields.first_name.hidden).toBe(false); expect(Store.state.publish.base.hiddenFields.last_name.hidden).toBe('force'); + expect(Store.state.publish.base.hiddenFields.first_name.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.last_name.omitValue).toBe(false); }); test('it tells omitter to omit hidden fields by default', async () => { @@ -456,14 +458,33 @@ test('it tells omitter to omit hidden fields by default', async () => { }); // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... - Fields.showField({handle: 'is_online_event'}); - Fields.showField({handle: 'event_venue', if: {is_online_event: true}}); + expect(Fields.showField({handle: 'is_online_event'})).toBe(true); + expect(Fields.showField({handle: 'event_venue', if: {is_online_event: true}})).toBe(false); await Vue.nextTick(); + expect(Store.state.publish.base.hiddenFields.is_online_event.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue.hidden).toBe(true); expect(Store.state.publish.base.hiddenFields.is_online_event.omitValue).toBe(false); expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(true); }); +test('it tells omitter to omit revealer fields', async () => { + Fields.setValues({ + revealer_toggle: false, + regular_toggle: false, + }); + + // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + expect(Fields.showField({handle: 'revealer_toggle', type: 'revealer'})).toBe(true); + expect(Fields.showField({handle: 'regular_toggle', type: 'toggle'})).toBe(true); + await Vue.nextTick(); + + expect(Store.state.publish.base.hiddenFields.revealer_toggle.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.regular_toggle.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.revealer_toggle.omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields.regular_toggle.omitValue).toBe(false); +}); + test('it tells omitter not omit revealer hidden fields', async () => { Fields.setValues({ show_more_info: false, @@ -473,10 +494,12 @@ test('it tells omitter not omit revealer hidden fields', async () => { Fields.setRevealerField('show_more_info'); // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... - Fields.showField({handle: 'show_more_info'}); - Fields.showField({handle: 'event_venue', if: {show_more_info: true}}); + expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); + expect(Fields.showField({handle: 'event_venue', if: {show_more_info: true}})).toBe(false); await Vue.nextTick(); - expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.show_more_info.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue.hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(true); expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(false); }); From b032d9e0185a823ed5fc05f815906f4ec65602f5 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 2 Aug 2022 16:55:27 -0400 Subject: [PATCH 08/17] Write failing test for revealers when multiple conditions are set. --- .../js/tests/FieldConditionsValidator.test.js | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 2594595fd8c..2a4fa972e4b 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -485,7 +485,7 @@ test('it tells omitter to omit revealer fields', async () => { expect(Store.state.publish.base.hiddenFields.regular_toggle.omitValue).toBe(false); }); -test('it tells omitter not omit revealer hidden fields', async () => { +test('it tells omitter not omit revealer-hidden fields', async () => { Fields.setValues({ show_more_info: false, event_venue: false, @@ -503,3 +503,41 @@ test('it tells omitter not omit revealer hidden fields', async () => { expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(true); expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(false); }); + +test('it properly handles and omits values hidden by revealers and omit when multiple conditions are set', async () => { + Fields.setValues({ + show_more_info: false, + has_second_event_venue: true, + has_third_event_venue: false, + event_venue_one: 'Stadium One', + event_venue_two: 'Stadium Two', + event_venue_three: false, + }); + + Fields.setRevealerField('show_more_info'); + + // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); + expect(Fields.showField({handle: 'has_second_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); + expect(Fields.showField({handle: 'has_third_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); + expect(Fields.showField({handle: 'event_venue_one', if: {show_more_info: true}})).toBe(false); + expect(Fields.showField({handle: 'event_venue_two', if: {show_more_info: true, has_second_event_venue: true}})).toBe(false); + expect(Fields.showField({handle: 'event_venue_three', if: {show_more_info: true, has_third_event_venue: true}})).toBe(false); + await Vue.nextTick(); + + expect(Store.state.publish.base.hiddenFields.show_more_info.hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields.has_second_event_venue.hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields.has_third_event_venue.hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields.event_venue_one.hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields.event_venue_two.hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields.event_venue_three.hidden).toBe(true); + + expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields.has_second_event_venue.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.has_third_event_venue.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue_one.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields.event_venue_two.omitValue).toBe(false); + + // Though this third venue is hidden by a revealer, it's also disabled by a regular toggle condition, so it should actually be omitted... + expect(Store.state.publish.base.hiddenFields.event_venue_three.omitValue).toBe(true); +}); From 544b473f5efed4fe14aca7aea3548e587218984f Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 3 Aug 2022 17:10:02 -0400 Subject: [PATCH 09/17] Pass failing test. --- .../js/components/field-conditions/Validator.js | 17 +++++++++++++---- .../field-conditions/ValidatorMixin.js | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/resources/js/components/field-conditions/Validator.js b/resources/js/components/field-conditions/Validator.js index 860223184e8..b9acf37e36e 100644 --- a/resources/js/components/field-conditions/Validator.js +++ b/resources/js/components/field-conditions/Validator.js @@ -9,11 +9,12 @@ import map from 'underscore/modules/map.js' import each from 'underscore/modules/each.js' import filter from 'underscore/modules/filter.js' import reject from 'underscore/modules/reject.js' +import omit from 'underscore/modules/omit.js' import first from 'underscore/modules/first.js' import chain from 'underscore/modules/chain.js' import chainable from 'underscore/modules/mixin.js' -chainable({ chain, map, each, filter, reject, first, isEmpty }); +chainable({ chain, map, each, filter, reject, omit, first, isEmpty }); const NUMBER_SPECIFIC_COMPARISONS = [ '>', '>=', '<', '<=' @@ -31,8 +32,8 @@ export default class { this.converter = new Converter; } - passesConditions() { - let conditions = this.getConditions(); + passesConditions(specificConditions) { + let conditions = specificConditions || this.getConditions(); if (conditions === undefined) { return true; @@ -283,7 +284,7 @@ export default class { return checkedFields; } - isHiddenByRevealer(dottedPrefix) { + isHiddenByRevealerOnly(dottedPrefix) { if (! this.store || ! this.storeName) { return false; } @@ -294,6 +295,14 @@ export default class { return false; } + let nonRevealerConditions = chain(this.getConditions()) + .omit((rhs, lhs) => revealerFields.includes(lhs)) + .value(); + + if (! this.passesConditions(nonRevealerConditions)) { + return false; + } + return intersection(this.getCheckedFieldPaths(dottedPrefix), revealerFields).length > 0; } } diff --git a/resources/js/components/field-conditions/ValidatorMixin.js b/resources/js/components/field-conditions/ValidatorMixin.js index faaf14cc1b3..c474fd5b4c1 100644 --- a/resources/js/components/field-conditions/ValidatorMixin.js +++ b/resources/js/components/field-conditions/ValidatorMixin.js @@ -33,7 +33,7 @@ export default { this.$store.commit(`publish/${this.storeName}/setHiddenField`, { dottedKey: dottedFieldPath, hidden: ! passes, - omitValue: field.type === 'revealer' || (! passes && ! validator.isHiddenByRevealer(dottedPrefix)), + omitValue: field.type === 'revealer' || (! passes && ! validator.isHiddenByRevealerOnly(dottedPrefix)), }); }); From 658b0dd5e3cc4c803a4d271d9ccb074f9eb1d3b8 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Wed, 3 Aug 2022 19:17:18 -0400 Subject: [PATCH 10/17] Not needed. --- resources/js/tests/FieldConditionsValidator.test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 2a4fa972e4b..65bbb238b37 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -26,9 +26,6 @@ const Store = new Vuex.Store({ values: {}, hiddenFields: {}, revealerFields: [], - // statamic: { - // conditions: {}, - // }, }, mutations: { setValues(state, values) { From 156b6ed78769c9d8df748301912cfb5c646f312e Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 4 Aug 2022 09:20:35 -0400 Subject: [PATCH 11/17] Misc cleanup. --- resources/js/tests/FieldConditionsValidator.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 65bbb238b37..810f327885a 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -3,7 +3,6 @@ import Vuex from 'vuex'; import ValidatesFieldConditions from '../components/field-conditions/ValidatorMixin.js'; Vue.use(Vuex); - const Store = new Vuex.Store({ modules: { statamic: { @@ -471,7 +470,7 @@ test('it tells omitter to omit revealer fields', async () => { regular_toggle: false, }); - // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... expect(Fields.showField({handle: 'revealer_toggle', type: 'revealer'})).toBe(true); expect(Fields.showField({handle: 'regular_toggle', type: 'toggle'})).toBe(true); await Vue.nextTick(); @@ -490,7 +489,7 @@ test('it tells omitter not omit revealer-hidden fields', async () => { Fields.setRevealerField('show_more_info'); - // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); expect(Fields.showField({handle: 'event_venue', if: {show_more_info: true}})).toBe(false); await Vue.nextTick(); @@ -513,7 +512,7 @@ test('it properly handles and omits values hidden by revealers and omit when mul Fields.setRevealerField('show_more_info'); - // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... + // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); expect(Fields.showField({handle: 'has_second_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); expect(Fields.showField({handle: 'has_third_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); From 408df73769146cf21e073f17e7bdcb33c8f5792c Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 4 Aug 2022 09:22:47 -0400 Subject: [PATCH 12/17] Improve nested field validation test, in prep for other tests. --- .../js/tests/FieldConditionsValidator.test.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 810f327885a..37b55f55d3d 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -66,6 +66,13 @@ const Fields = new Vue({ this.values = values; Store.commit('publish/base/setValues', values); }, + setNestedValues(nestedKey, values) { + this.values = values; + + let storeValues = {}; + storeValues[nestedKey] = values; + Store.commit('publish/base/setValues', storeValues); + }, setStoreValues(values) { Store.commit('publish/base/setValues', values); }, @@ -291,16 +298,16 @@ test('it shows or hides when any of the conditions are met', () => { }); test('it can run conditions on nested data', () => { - Fields.setValues({ - user: { - address: { - country: 'Canada' - } + Fields.setNestedValues('user', { + address: { + country: 'Canada' } }); - expect(showFieldIf({'user.address.country': 'Canada'})).toBe(true); - expect(showFieldIf({'user.address.country': 'Australia'})).toBe(false); + expect(showFieldIf({'address.country': 'Canada'})).toBe(true); + expect(showFieldIf({'address.country': 'Australia'})).toBe(false); + expect(showFieldIf({'root.user.address.country': 'Canada'})).toBe(true); + expect(showFieldIf({'root.user.address.country': 'Australia'})).toBe(false); }); test('it can run conditions on root store values', () => { From 0cb35893149cfa983e6a56103203f3d88d288c19 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 4 Aug 2022 10:45:32 -0400 Subject: [PATCH 13/17] Cleanup and add more test coverage for nested conditions and nested revealers. --- .../js/tests/FieldConditionsValidator.test.js | 237 ++++++++++++------ 1 file changed, 164 insertions(+), 73 deletions(-) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index 37b55f55d3d..bfc14c0d57c 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -36,8 +36,8 @@ const Store = new Vuex.Store({ omitValue: field.omitValue, }; }, - setRevealerField(state, field) { - state.revealerFields.push(field.dottedKey); + setRevealerField(state, dottedKey) { + state.revealerFields.push(dottedKey); }, } } @@ -62,15 +62,14 @@ const Fields = new Vue({ } }, methods: { - setValues(values) { + setValues(values, nestedKey) { this.values = values; - Store.commit('publish/base/setValues', values); - }, - setNestedValues(nestedKey, values) { - this.values = values; - let storeValues = {}; - storeValues[nestedKey] = values; + if (nestedKey) { + storeValues[nestedKey] = values; + } else { + storeValues = values; + } Store.commit('publish/base/setValues', storeValues); }, setStoreValues(values) { @@ -79,8 +78,14 @@ const Fields = new Vue({ setHiddenField(payload) { Store.commit('publish/base/setHiddenField', payload); }, - setRevealerField(dottedKey) { - Store.commit('publish/base/setRevealerField', {dottedKey}); + setHiddenFieldsState: async (fieldConfigs, dottedPrefix) => { + fieldConfigs.filter(fieldConfig => fieldConfig.type === 'revealer').forEach(fieldConfig => { + Store.commit('publish/base/setRevealerField', dottedPrefix ? `${dottedPrefix}.${fieldConfig.handle}`: fieldConfig.handle) + }); + fieldConfigs.forEach(fieldConfig => { + Fields.showField(fieldConfig, dottedPrefix ? `${dottedPrefix}.${fieldConfig.handle}`: null) + }); + await Vue.nextTick(); }, } }); @@ -298,12 +303,15 @@ test('it shows or hides when any of the conditions are met', () => { }); test('it can run conditions on nested data', () => { - Fields.setNestedValues('user', { + Fields.setValues({ + name: 'Han', address: { country: 'Canada' } - }); + }, 'user'); + expect(showFieldIf({'name': 'Han'})).toBe(true); + expect(showFieldIf({'name': 'Chewy'})).toBe(false); expect(showFieldIf({'address.country': 'Canada'})).toBe(true); expect(showFieldIf({'address.country': 'Australia'})).toBe(false); expect(showFieldIf({'root.user.address.country': 'Canada'})).toBe(true); @@ -443,32 +451,49 @@ test('it can externally force hide a field before validator conditions are evalu }); test('it force hides fields with hidden visibility config', async () => { - // Triggering these showField() checks and waiting a tick should set their `hidden` state in the store... - expect(Fields.showField({handle: 'first_name'})).toBe(true); - expect(Fields.showField({handle: 'last_name', visibility: 'hidden'})).toBe(false); - await Vue.nextTick(); - - expect(Store.state.publish.base.hiddenFields.first_name.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.last_name.hidden).toBe('force'); - expect(Store.state.publish.base.hiddenFields.first_name.omitValue).toBe(false); - expect(Store.state.publish.base.hiddenFields.last_name.omitValue).toBe(false); + await Fields.setHiddenFieldsState([ + {handle: 'first_name'}, + {handle: 'last_name', visibility: 'hidden'}, + ]); + + expect(Store.state.publish.base.hiddenFields['first_name'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['last_name'].hidden).toBe('force'); + expect(Store.state.publish.base.hiddenFields['first_name'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['last_name'].omitValue).toBe(false); }); test('it tells omitter to omit hidden fields by default', async () => { Fields.setValues({ is_online_event: false, - event_venue: false, + venue: false, }); - // Triggering these showField() checks and waiting a tick should set their `omitValue` state in the store... - expect(Fields.showField({handle: 'is_online_event'})).toBe(true); - expect(Fields.showField({handle: 'event_venue', if: {is_online_event: true}})).toBe(false); - await Vue.nextTick(); + await Fields.setHiddenFieldsState([ + {handle: 'is_online_event'}, + {handle: 'venue', if: {is_online_event: true}}, + ]); + + expect(Store.state.publish.base.hiddenFields['is_online_event'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['is_online_event'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['venue'].omitValue).toBe(true); +}); + +test('it tells omitter to omit nested hidden fields by default', async () => { + Fields.setValues({ + is_online_event: false, + event_venue: false, + }, 'nested'); - expect(Store.state.publish.base.hiddenFields.is_online_event.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.event_venue.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.is_online_event.omitValue).toBe(false); - expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(true); + await Fields.setHiddenFieldsState([ + {handle: 'is_online_event'}, + {handle: 'venue', if: {is_online_event: true}}, + ], 'nested'); + + expect(Store.state.publish.base.hiddenFields['nested.is_online_event'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.is_online_event'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.venue'].omitValue).toBe(true); }); test('it tells omitter to omit revealer fields', async () => { @@ -477,15 +502,32 @@ test('it tells omitter to omit revealer fields', async () => { regular_toggle: false, }); - // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... - expect(Fields.showField({handle: 'revealer_toggle', type: 'revealer'})).toBe(true); - expect(Fields.showField({handle: 'regular_toggle', type: 'toggle'})).toBe(true); - await Vue.nextTick(); + await Fields.setHiddenFieldsState([ + {handle: 'revealer_toggle', type: 'revealer'}, + {handle: 'regular_toggle', type: 'regular'}, + ]); + + expect(Store.state.publish.base.hiddenFields['revealer_toggle'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['regular_toggle'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['revealer_toggle'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['regular_toggle'].omitValue).toBe(false); +}); + +test('it tells omitter to omit nested revealer fields', async () => { + Fields.setValues({ + revealer_toggle: false, + regular_toggle: false, + }, 'nested'); + + await Fields.setHiddenFieldsState([ + {handle: 'revealer_toggle', type: 'revealer'}, + {handle: 'regular_toggle', type: 'regular'}, + ], 'nested'); - expect(Store.state.publish.base.hiddenFields.revealer_toggle.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.regular_toggle.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.revealer_toggle.omitValue).toBe(true); - expect(Store.state.publish.base.hiddenFields.regular_toggle.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.revealer_toggle'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.regular_toggle'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.revealer_toggle'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.regular_toggle'].omitValue).toBe(false); }); test('it tells omitter not omit revealer-hidden fields', async () => { @@ -494,20 +536,35 @@ test('it tells omitter not omit revealer-hidden fields', async () => { event_venue: false, }); - Fields.setRevealerField('show_more_info'); + await Fields.setHiddenFieldsState([ + {handle: 'show_more_info', type: 'revealer'}, + {handle: 'venue', if: {show_more_info: true}}, + ]); + + expect(Store.state.publish.base.hiddenFields['show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['venue'].omitValue).toBe(false); +}); + +test('it tells omitter not omit nested revealer-hidden fields', async () => { + Fields.setValues({ + show_more_info: false, + event_venue: false, + }, 'nested'); - // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... - expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); - expect(Fields.showField({handle: 'event_venue', if: {show_more_info: true}})).toBe(false); - await Vue.nextTick(); + await Fields.setHiddenFieldsState([ + {handle: 'show_more_info', type: 'revealer'}, + {handle: 'venue', if: {show_more_info: true}}, + ], 'nested'); - expect(Store.state.publish.base.hiddenFields.show_more_info.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.event_venue.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(true); - expect(Store.state.publish.base.hiddenFields.event_venue.omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.venue'].omitValue).toBe(false); }); -test('it properly handles and omits values hidden by revealers and omit when multiple conditions are set', async () => { +test('it properly omits revealer-hidden fields when multiple conditions are set', async () => { Fields.setValues({ show_more_info: false, has_second_event_venue: true, @@ -517,30 +574,64 @@ test('it properly handles and omits values hidden by revealers and omit when mul event_venue_three: false, }); - Fields.setRevealerField('show_more_info'); - - // Triggering these showField() checks and waiting a tick should set their `hiddenFields` state in the store... - expect(Fields.showField({handle: 'show_more_info', type: 'revealer'})).toBe(true); - expect(Fields.showField({handle: 'has_second_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); - expect(Fields.showField({handle: 'has_third_event_venue', type: 'toggle', if: {show_more_info: true}})).toBe(false); - expect(Fields.showField({handle: 'event_venue_one', if: {show_more_info: true}})).toBe(false); - expect(Fields.showField({handle: 'event_venue_two', if: {show_more_info: true, has_second_event_venue: true}})).toBe(false); - expect(Fields.showField({handle: 'event_venue_three', if: {show_more_info: true, has_third_event_venue: true}})).toBe(false); - await Vue.nextTick(); - - expect(Store.state.publish.base.hiddenFields.show_more_info.hidden).toBe(false); - expect(Store.state.publish.base.hiddenFields.has_second_event_venue.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.has_third_event_venue.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.event_venue_one.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.event_venue_two.hidden).toBe(true); - expect(Store.state.publish.base.hiddenFields.event_venue_three.hidden).toBe(true); - - expect(Store.state.publish.base.hiddenFields.show_more_info.omitValue).toBe(true); - expect(Store.state.publish.base.hiddenFields.has_second_event_venue.omitValue).toBe(false); - expect(Store.state.publish.base.hiddenFields.has_third_event_venue.omitValue).toBe(false); - expect(Store.state.publish.base.hiddenFields.event_venue_one.omitValue).toBe(false); - expect(Store.state.publish.base.hiddenFields.event_venue_two.omitValue).toBe(false); + await Fields.setHiddenFieldsState([ + {handle: 'show_more_info', type: 'revealer'}, + {handle: 'has_second_event_venue', type: 'toggle', if: {show_more_info: true}}, + {handle: 'has_third_event_venue', type: 'toggle', if: {show_more_info: true}}, + {handle: 'event_venue_one', if: {show_more_info: true}}, + {handle: 'event_venue_two', if: {show_more_info: true, has_second_event_venue: true}}, + {handle: 'event_venue_three', if: {show_more_info: true, has_third_event_venue: true}}, + ]); + + expect(Store.state.publish.base.hiddenFields['show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['has_second_event_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['has_third_event_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['event_venue_one'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['event_venue_two'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['event_venue_three'].hidden).toBe(true); + + expect(Store.state.publish.base.hiddenFields['show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['has_second_event_venue'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['has_third_event_venue'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['event_venue_one'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['event_venue_two'].omitValue).toBe(false); + + // Though this third venue is hidden by a revealer, it's also disabled by a regular toggle condition, so it should actually be omitted... + expect(Store.state.publish.base.hiddenFields['event_venue_three'].omitValue).toBe(true); +}); + +test('it properly omits nested revealer-hidden fields when multiple conditions are set', async () => { + Fields.setValues({ + show_more_info: false, + has_second_event_venue: true, + has_third_event_venue: false, + event_venue_one: 'Stadium One', + event_venue_two: 'Stadium Two', + event_venue_three: false, + }, 'nested'); + + await Fields.setHiddenFieldsState([ + {handle: 'show_more_info', type: 'revealer'}, + {handle: 'has_second_event_venue', type: 'toggle', if: {show_more_info: true}}, + {handle: 'has_third_event_venue', type: 'toggle', if: {show_more_info: true}}, + {handle: 'event_venue_one', if: {show_more_info: true}}, + {handle: 'event_venue_two', if: {show_more_info: true, has_second_event_venue: true}}, + {handle: 'event_venue_three', if: {show_more_info: true, has_third_event_venue: true}}, + ], 'nested'); + + expect(Store.state.publish.base.hiddenFields['nested.show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.has_second_event_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.has_third_event_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_one'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_two'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_three'].hidden).toBe(true); + + expect(Store.state.publish.base.hiddenFields['nested.show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.has_second_event_venue'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.has_third_event_venue'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_one'].omitValue).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_two'].omitValue).toBe(false); // Though this third venue is hidden by a revealer, it's also disabled by a regular toggle condition, so it should actually be omitted... - expect(Store.state.publish.base.hiddenFields.event_venue_three.omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.event_venue_three'].omitValue).toBe(true); }); From 9d66553661a6d624bd7af8f06f5310c6064e86bb Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 4 Aug 2022 10:46:58 -0400 Subject: [PATCH 14/17] Drastically clean up revealer condition logic. --- .../components/field-conditions/Validator.js | 46 +++++-------------- .../field-conditions/ValidatorMixin.js | 2 +- 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/resources/js/components/field-conditions/Validator.js b/resources/js/components/field-conditions/Validator.js index b9acf37e36e..0d95e1318f8 100644 --- a/resources/js/components/field-conditions/Validator.js +++ b/resources/js/components/field-conditions/Validator.js @@ -262,47 +262,23 @@ export default class { return this.showOnPass ? passes : ! passes; } - getCheckedFieldPaths(dottedPrefix) { - let conditions = this.getConditions(); - - if (conditions === undefined || isString(conditions)) { - return false; - } - - let checkedFields = this.converter - .fromBlueprint(conditions, this.field.prefix) - .map(field => field.field); - - if (dottedPrefix) { - checkedFields = checkedFields.map(field => { - return field.startsWith('root.') - ? field.replace(/^root\./, '') - : dottedPrefix + '.' + field; - }); - } - - return checkedFields; - } - - isHiddenByRevealerOnly(dottedPrefix) { - if (! this.store || ! this.storeName) { - return false; - } - + passesNonRevealerConditions(dottedPrefix) { let revealerFields = data_get(this.store.state.publish[this.storeName], 'revealerFields', []); - if (! revealerFields.length) { - return false; - } - let nonRevealerConditions = chain(this.getConditions()) - .omit((rhs, lhs) => revealerFields.includes(lhs)) + .omit((rhs, lhs) => revealerFields.includes(this.relativeLhsToAbsoluteFieldPath(lhs, dottedPrefix))) .value(); - if (! this.passesConditions(nonRevealerConditions)) { - return false; + return this.passesConditions(nonRevealerConditions); + } + + relativeLhsToAbsoluteFieldPath(lhs, dottedPrefix) { + if (! dottedPrefix) { + return lhs; } - return intersection(this.getCheckedFieldPaths(dottedPrefix), revealerFields).length > 0; + return lhs.startsWith('root.') + ? lhs.replace(/^root\./, '') + : dottedPrefix + '.' + lhs; } } diff --git a/resources/js/components/field-conditions/ValidatorMixin.js b/resources/js/components/field-conditions/ValidatorMixin.js index c474fd5b4c1..4d3c1ee154b 100644 --- a/resources/js/components/field-conditions/ValidatorMixin.js +++ b/resources/js/components/field-conditions/ValidatorMixin.js @@ -33,7 +33,7 @@ export default { this.$store.commit(`publish/${this.storeName}/setHiddenField`, { dottedKey: dottedFieldPath, hidden: ! passes, - omitValue: field.type === 'revealer' || (! passes && ! validator.isHiddenByRevealerOnly(dottedPrefix)), + omitValue: field.type === 'revealer' || ! validator.passesNonRevealerConditions(dottedPrefix), }); }); From 1650e8425439beb710d07bc8ed834ac6e3c599ef Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 5 Aug 2022 18:36:48 -0400 Subject: [PATCH 15/17] Add test coverage for prefixed fields and prefixed revealers. --- .../js/tests/FieldConditionsValidator.test.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/resources/js/tests/FieldConditionsValidator.test.js b/resources/js/tests/FieldConditionsValidator.test.js index bfc14c0d57c..322e6a8d1a4 100644 --- a/resources/js/tests/FieldConditionsValidator.test.js +++ b/resources/js/tests/FieldConditionsValidator.test.js @@ -39,6 +39,11 @@ const Store = new Vuex.Store({ setRevealerField(state, dottedKey) { state.revealerFields.push(dottedKey); }, + reset(state) { + state.values = {}; + state.hiddenFields = {}; + state.revealerFields = []; + }, } } } @@ -96,6 +101,7 @@ let showFieldIf = function (conditions=null) { afterEach(() => { Fields.values = {}; + Store.commit('publish/base/reset'); }); test('it shows field by default', () => { @@ -327,6 +333,28 @@ test('it can run conditions on root store values', () => { expect(showFieldIf({'root.favorite_foods': 'contains lasagna'})).toBe(true); }); +test('it can run conditions on prefixed fields', async () => { + Fields.setValues({ + prefixed_first_name: 'Rincess', + prefixed_last_name: 'Pleia' + }); + + expect(Fields.showField({prefix: 'prefixed_', if: {first_name: 'is Rincess', last_name: 'is Pleia'}})).toBe(true); + expect(Fields.showField({prefix: 'prefixed_', if: {first_name: 'is Rincess', last_name: 'is Holo'}})).toBe(false); +}); + +test('it can run conditions on nested prefixed fields', async () => { + Fields.setValues({ + prefixed_first_name: 'Rincess', + prefixed_last_name: 'Pleia' + }, 'nested'); + + expect(Fields.showField({prefix: 'prefixed_', if: {first_name: 'is Rincess', last_name: 'is Pleia'}})).toBe(true); + expect(Fields.showField({prefix: 'prefixed_', if: {first_name: 'is Rincess', last_name: 'is Holo'}})).toBe(false); + expect(Fields.showField({if: {'root.nested.prefixed_last_name': 'is Pleia'}})).toBe(true); + expect(Fields.showField({if: {'root.nested.prefixed_last_name': 'is Holo'}})).toBe(false); +}); + test('it can call a custom function', () => { Fields.setValues({ favorite_animals: ['cats', 'dogs'], @@ -564,6 +592,40 @@ test('it tells omitter not omit nested revealer-hidden fields', async () => { expect(Store.state.publish.base.hiddenFields['nested.venue'].omitValue).toBe(false); }); +test('it tells omitter not omit prefixed revealer-hidden fields', async () => { + Fields.setValues({ + prefixed_show_more_info: false, + prefixed_event_venue: false, + }); + + await Fields.setHiddenFieldsState([ + {handle: 'prefixed_show_more_info', prefix: 'prefixed_', type: 'revealer'}, + {handle: 'prefixed_venue', prefix: 'prefixed_', if: {show_more_info: true}}, + ]); + + expect(Store.state.publish.base.hiddenFields['prefixed_show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['prefixed_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['prefixed_show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['prefixed_venue'].omitValue).toBe(false); +}); + +test('it tells omitter not omit nested prefixed revealer-hidden fields', async () => { + Fields.setValues({ + prefixed_show_more_info: false, + prefixed_event_venue: false, + }, 'nested'); + + await Fields.setHiddenFieldsState([ + {handle: 'prefixed_show_more_info', prefix: 'prefixed_', type: 'revealer'}, + {handle: 'prefixed_venue', prefix: 'prefixed_', if: {show_more_info: true}}, + ], 'nested'); + + expect(Store.state.publish.base.hiddenFields['nested.prefixed_show_more_info'].hidden).toBe(false); + expect(Store.state.publish.base.hiddenFields['nested.prefixed_venue'].hidden).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.prefixed_show_more_info'].omitValue).toBe(true); + expect(Store.state.publish.base.hiddenFields['nested.prefixed_venue'].omitValue).toBe(false); +}); + test('it properly omits revealer-hidden fields when multiple conditions are set', async () => { Fields.setValues({ show_more_info: false, From 7e88079e7310efcc5fccef5f65cc5e72352f6256 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 5 Aug 2022 18:37:41 -0400 Subject: [PATCH 16/17] Pass tests again by moving prefix handling up a level into `getConditions()`. --- .../components/field-conditions/Validator.js | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/resources/js/components/field-conditions/Validator.js b/resources/js/components/field-conditions/Validator.js index 0d95e1318f8..f1d197f50a1 100644 --- a/resources/js/components/field-conditions/Validator.js +++ b/resources/js/components/field-conditions/Validator.js @@ -9,12 +9,11 @@ import map from 'underscore/modules/map.js' import each from 'underscore/modules/each.js' import filter from 'underscore/modules/filter.js' import reject from 'underscore/modules/reject.js' -import omit from 'underscore/modules/omit.js' import first from 'underscore/modules/first.js' import chain from 'underscore/modules/chain.js' import chainable from 'underscore/modules/mixin.js' -chainable({ chain, map, each, filter, reject, omit, first, isEmpty }); +chainable({ chain, map, each, filter, reject, first, isEmpty }); const NUMBER_SPECIFIC_COMPARISONS = [ '>', '>=', '<', '<=' @@ -37,12 +36,10 @@ export default class { if (conditions === undefined) { return true; - } else if (isString(conditions)) { + } else if (this.isCustomCondition(conditions)) { return this.passesCustomCondition(this.prepareCondition(conditions)); } - conditions = this.converter.fromBlueprint(conditions, this.field.prefix); - let passes = this.passOnAny ? this.passesAnyConditions(conditions) : this.passesAllConditions(conditions); @@ -68,7 +65,15 @@ export default class { this.showOnPass = false; } - return this.field[key]; + let conditions = this.field[key]; + + return this.isCustomCondition(conditions) + ? conditions + : this.converter.fromBlueprint(conditions, this.field.prefix); + } + + isCustomCondition(conditions) { + return isString(conditions); } passesAllConditions(conditions) { @@ -263,10 +268,16 @@ export default class { } passesNonRevealerConditions(dottedPrefix) { + let conditions = this.getConditions(); + + if (this.isCustomCondition(conditions)) { + return this.passesConditions(conditions); + } + let revealerFields = data_get(this.store.state.publish[this.storeName], 'revealerFields', []); let nonRevealerConditions = chain(this.getConditions()) - .omit((rhs, lhs) => revealerFields.includes(this.relativeLhsToAbsoluteFieldPath(lhs, dottedPrefix))) + .reject(condition => revealerFields.includes(this.relativeLhsToAbsoluteFieldPath(condition.field, dottedPrefix))) .value(); return this.passesConditions(nonRevealerConditions); From 6deee59e3c9f92ea08a701a36b12b118f5564791 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Fri, 5 Aug 2022 19:46:55 -0400 Subject: [PATCH 17/17] Rename method for clarity with terminology in docs. --- resources/js/components/field-conditions/Validator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/js/components/field-conditions/Validator.js b/resources/js/components/field-conditions/Validator.js index f1d197f50a1..3492158eb7f 100644 --- a/resources/js/components/field-conditions/Validator.js +++ b/resources/js/components/field-conditions/Validator.js @@ -36,7 +36,7 @@ export default class { if (conditions === undefined) { return true; - } else if (this.isCustomCondition(conditions)) { + } else if (this.isCustomConditionWithoutTarget(conditions)) { return this.passesCustomCondition(this.prepareCondition(conditions)); } @@ -67,12 +67,12 @@ export default class { let conditions = this.field[key]; - return this.isCustomCondition(conditions) + return this.isCustomConditionWithoutTarget(conditions) ? conditions : this.converter.fromBlueprint(conditions, this.field.prefix); } - isCustomCondition(conditions) { + isCustomConditionWithoutTarget(conditions) { return isString(conditions); } @@ -270,7 +270,7 @@ export default class { passesNonRevealerConditions(dottedPrefix) { let conditions = this.getConditions(); - if (this.isCustomCondition(conditions)) { + if (this.isCustomConditionWithoutTarget(conditions)) { return this.passesConditions(conditions); }