Skip to content

Commit bc36961

Browse files
authored
Merge pull request #4141 from 10up/feature/issue-4123-req-3
Feature Field Dependency (Issue 4123 Req 3)
2 parents 7873e6f + e724c95 commit bc36961

File tree

2 files changed

+124
-4
lines changed

2 files changed

+124
-4
lines changed

assets/js/features/components/settings.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Control from './control';
2020
export default ({ feature, settingsSchema }) => {
2121
const { getFeature, settings, setSettings, syncedSettings } = useFeatureSettings();
2222

23-
const { isAvailable } = getFeature(feature);
23+
const { isAvailable, defaultSettings } = getFeature(feature);
2424

2525
/**
2626
* Change event handler.
@@ -38,6 +38,51 @@ export default ({ feature, settingsSchema }) => {
3838
});
3939
};
4040

41+
/**
42+
* Determines whether a control should be rendered based on its requirements.
43+
*
44+
* @param {object} requires_fields An object representing the required field values for rendering.
45+
* Can contain 'conditions' object with field requirements and 'relationship' key ('AND' or 'OR').
46+
* @returns {boolean} Returns `true` if the control should be rendered, otherwise `false`.
47+
*/
48+
const shouldRenderControl = (requires_fields) => {
49+
if (!requires_fields || Object.keys(requires_fields).length === 0) {
50+
return true;
51+
}
52+
53+
// Get field requirements from 'conditions' key
54+
let fieldRequirements;
55+
56+
if (requires_fields.conditions) {
57+
fieldRequirements = Object.entries(requires_fields.conditions);
58+
}
59+
60+
// If no actual field requirements, return true
61+
if (fieldRequirements.length === 0) {
62+
return true;
63+
}
64+
65+
// Define the condition check function
66+
const checkCondition = ([fieldKey, requiredValue]) => {
67+
const actualValue = settings[feature]?.[fieldKey];
68+
const defaultValue = defaultSettings[fieldKey] ?? false;
69+
return actualValue === requiredValue ?? actualValue === defaultValue;
70+
};
71+
72+
// Extract relationship type, default to 'AND'
73+
const relationship = (requires_fields.relationship || 'AND').toUpperCase();
74+
75+
// Apply the appropriate logic based on relationship type
76+
switch (relationship) {
77+
case 'OR':
78+
return fieldRequirements.some(checkCondition);
79+
case 'AND':
80+
default:
81+
// Default to AND for any unexpected values
82+
return fieldRequirements.every(checkCondition);
83+
}
84+
};
85+
4186
return settingsSchema.map((s) => {
4287
const {
4388
default: defaultValue,
@@ -48,13 +93,17 @@ export default ({ feature, settingsSchema }) => {
4893
options,
4994
requires_feature,
5095
requires_sync,
96+
requires_fields,
5197
type,
5298
} = s;
5399

54100
/**
55-
* Current control value. If no setting value is set, use the
56-
* setting's default value.
101+
* Skip rendering if the control should not be rendered based on requires_fields.
57102
*/
103+
if (!shouldRenderControl(requires_fields)) {
104+
return null;
105+
}
106+
58107
let value =
59108
typeof settings[feature]?.[key] !== 'undefined' ? settings[feature][key] : defaultValue;
60109

tests/cypress/integration/features/interface.cy.js

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('Feature Grouping and Persistence', () => {
1111
*/
1212
const panelSelector = 'div[id*="Live Search-view"]:has(.is-opened)';
1313

14-
it('Renders group tabs and persists across reloads', () => {
14+
it('Renders group tabs, persists across reloads, and supports field dependency', () => {
1515
// Log in as an admin user (assumes cy.login() is a custom Cypress command).
1616
cy.login();
1717

@@ -52,5 +52,76 @@ describe('Feature Grouping and Persistence', () => {
5252
cy.get('div[id*="autosuggest-view"]').should('be.visible');
5353
});
5454
});
55+
56+
cy.visit('/wp-admin/admin.php?page=elasticpress');
57+
58+
// Test case to verify a conditional feature is hidden until its requirement is met
59+
cy.window()
60+
.then((win) => {
61+
// Wait until epDashboard and features are available
62+
return new Cypress.Promise((resolve) => {
63+
const check = () => {
64+
if (win.epDashboard && win.epDashboard.features) {
65+
resolve(win);
66+
} else {
67+
setTimeout(check, 50);
68+
}
69+
};
70+
check();
71+
});
72+
})
73+
.then((win) => {
74+
win.epDashboard.features[0].settingsSchema.push({
75+
default: '1',
76+
key: 'test_field',
77+
label: 'Testing Field 1',
78+
options: [
79+
{
80+
label: 'Option A',
81+
value: '0',
82+
},
83+
{
84+
label: 'Option B',
85+
value: '1',
86+
},
87+
],
88+
type: 'radio',
89+
});
90+
win.epDashboard.features[0].settingsSchema.push({
91+
default: '1',
92+
key: 'test_field_2',
93+
label: 'Testing Field 2',
94+
options: [
95+
{
96+
label: 'Option A',
97+
value: '0',
98+
},
99+
{
100+
label: 'Option B',
101+
value: '1',
102+
},
103+
],
104+
type: 'radio',
105+
requires_fields: {
106+
conditions: {
107+
test_field: '0',
108+
},
109+
},
110+
});
111+
});
112+
113+
// Toggle Re-Render
114+
cy.contains('button', 'Live Search').click();
115+
cy.contains('button', 'Core Search').click();
116+
117+
cy.contains('.ep-dashboard-control', 'Testing Field 2').should('not.exist');
118+
119+
cy.contains('.ep-dashboard-control', 'Testing Field 1').as('testField');
120+
121+
// inside of testField, find the input with the label "Option A" and click it
122+
cy.get('@testField').find('input[value="0"]').click();
123+
124+
// now, Testing Field 2 should be visible
125+
cy.contains('.ep-dashboard-control', 'Testing Field 2').should('exist');
55126
});
56127
});

0 commit comments

Comments
 (0)