Added conditional logic for state specific steering
This commit is contained in:
parent
1681d0cce7
commit
2e27b1fc44
3 changed files with 260 additions and 4 deletions
|
|
@ -141,6 +141,12 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
|
||||||
function processWidgetItemForReplacement(widgetModel, key) {
|
function processWidgetItemForReplacement(widgetModel, key) {
|
||||||
// If we have a string, and it needs to be replaced.
|
// If we have a string, and it needs to be replaced.
|
||||||
if (typeof widgetModel[key] === "string") {
|
if (typeof widgetModel[key] === "string") {
|
||||||
|
widgetModel[key] = processIfStatements(
|
||||||
|
widgetModel[key],
|
||||||
|
dynamicStrings.GLOBAL_STATE,
|
||||||
|
getStoreValueFromString
|
||||||
|
);
|
||||||
|
|
||||||
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
||||||
widgetModel[key] = mapStringToState(widgetModel[key]);
|
widgetModel[key] = mapStringToState(widgetModel[key]);
|
||||||
}
|
}
|
||||||
|
|
@ -221,6 +227,192 @@ function mapStringToState(str) {
|
||||||
return stringBuilder.trimStart();
|
return stringBuilder.trimStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStoreValueFromString(str) {
|
||||||
|
let storeOrStateObject = useMainStore();
|
||||||
|
for (const s of str.split('.')) {
|
||||||
|
if (s === 'getters') continue;
|
||||||
|
if (storeOrStateObject[s] != undefined) {
|
||||||
|
storeOrStateObject = storeOrStateObject[s];
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storeOrStateObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
// If Statement Processing Logic //
|
||||||
|
///////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive function - replaces all instances of if statements from the CMS that utilize the specified ifConditionKeyword
|
||||||
|
* @param {*} str string - Input string to be processed
|
||||||
|
* @param {*} ifConditionKeyword string - Defines which if statements to process ex: 'globalState'
|
||||||
|
* @param {*} replacePlaceholderCallback function - Callback to replace CMS placeholder values
|
||||||
|
* @returns The processed string
|
||||||
|
*/
|
||||||
|
export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) {
|
||||||
|
const containsRelevantIfStatement = new RegExp('{if:' + ifConditionKeyword + ':.+?}', 'g').test(
|
||||||
|
str
|
||||||
|
);
|
||||||
|
//str = str.replace(/\r?\n|\r/g, '');
|
||||||
|
|
||||||
|
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
|
||||||
|
if (!containsRelevantIfStatement || hasEmbeddedCrLf) {
|
||||||
|
return str;
|
||||||
|
} else {
|
||||||
|
const ifStatementRegexExpression = getIfStatementRegexExpression();
|
||||||
|
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
|
||||||
|
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
|
||||||
|
ifStatementRegexMatches,
|
||||||
|
ifConditionKeyword
|
||||||
|
);
|
||||||
|
executeIfStatementAndSetProcessedStrings(
|
||||||
|
completeIfStatementArray,
|
||||||
|
replacePlaceholderCallback
|
||||||
|
);
|
||||||
|
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
|
||||||
|
return processIfStatements(
|
||||||
|
reconstructedPostProcessedString,
|
||||||
|
ifConditionKeyword,
|
||||||
|
replacePlaceholderCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
|
||||||
|
let index = 0;
|
||||||
|
for (const match of matches) {
|
||||||
|
if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
|
||||||
|
let interiorIndex = 0;
|
||||||
|
let nestedLevel = 0;
|
||||||
|
let elseStatementIndex = null;
|
||||||
|
for (const interiorMatch of matches.slice(index + 1)) {
|
||||||
|
if (interiorMatch.groups.isIfStatement) {
|
||||||
|
if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nestedLevel++;
|
||||||
|
}
|
||||||
|
} else if (interiorMatch.groups.isElseStatement) {
|
||||||
|
if (!nestedLevel) {
|
||||||
|
elseStatementIndex = interiorIndex + 1;
|
||||||
|
}
|
||||||
|
} else if (interiorMatch.groups.isEndStatement) {
|
||||||
|
if (nestedLevel) {
|
||||||
|
nestedLevel--;
|
||||||
|
} else {
|
||||||
|
const ifStatementArray = matches.slice(index, index + interiorIndex + 2);
|
||||||
|
flagMatchesForProcessing(ifStatementArray, elseStatementIndex);
|
||||||
|
return ifStatementArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interiorIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
console.error('Did not find end in conditional logic');
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flagMatchesForProcessing(matches, elseStatementIndex) {
|
||||||
|
matches[0].isFlaggedForProcessing = true;
|
||||||
|
matches[matches.length - 1].isFlaggedForProcessing = true;
|
||||||
|
if (elseStatementIndex) {
|
||||||
|
matches[elseStatementIndex].isFlaggedForProcessing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinProcessedRegexArray(regexMatches) {
|
||||||
|
let processedString = '';
|
||||||
|
regexMatches.forEach((match) => {
|
||||||
|
const rawString = match[0];
|
||||||
|
processedString += match.groups.processedString ?? rawString;
|
||||||
|
});
|
||||||
|
return processedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
|
||||||
|
const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
|
||||||
|
let isInsideDesiredBlock = ifCondition;
|
||||||
|
|
||||||
|
ifStatementArray.forEach((entry) => {
|
||||||
|
if (entry.groups.isElseStatement && entry.isFlaggedForProcessing) {
|
||||||
|
isInsideDesiredBlock = !ifCondition;
|
||||||
|
} else if (entry.groups.isEndStatement && entry.isFlaggedForProcessing) {
|
||||||
|
isInsideDesiredBlock = true;
|
||||||
|
}
|
||||||
|
setProcessedStringOnEntry(entry, isInsideDesiredBlock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
|
||||||
|
if (!isInsideDesiredBlock) {
|
||||||
|
entry.groups.processedString = '';
|
||||||
|
} else {
|
||||||
|
if (entry.groups.isIfStatement) {
|
||||||
|
entry.groups.processedString = entry.isFlaggedForProcessing
|
||||||
|
? entry.groups.ifTrailingString
|
||||||
|
: entry[0];
|
||||||
|
} else if (entry.groups.isElseStatement) {
|
||||||
|
entry.groups.processedString = entry.isFlaggedForProcessing
|
||||||
|
? entry.groups.elseTrailingString
|
||||||
|
: entry[0];
|
||||||
|
} else {
|
||||||
|
entry.groups.processedString = entry.isFlaggedForProcessing
|
||||||
|
? entry.groups.endTrailingString
|
||||||
|
: entry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIfStatementRegexExpression() {
|
||||||
|
// Matches but does not capture:
|
||||||
|
// {if:...} or {else} or {end}
|
||||||
|
const anyLogicOperatorNonCapture = '(?:{(?:end|else|if:.*?)})';
|
||||||
|
// NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
|
||||||
|
const matchStartOfString =
|
||||||
|
'(?<processedString>^.+?)' + // Match and Capture all characters (lazy), cannot be empty
|
||||||
|
'(?=(?:{if))'; // Looks ahead but does not capture {if
|
||||||
|
const matchIfOperator =
|
||||||
|
'(?<isIfStatement>{if:)' + // Match & Capture {if:
|
||||||
|
'(?<ifConditionType>.*?):' + // Match all chars up to and including next ':' - Capture all chars up to ':'
|
||||||
|
'(?<ifCondition>.*?)}' + // Match all chars up to and including next '}' - Capture all chars up to '}'
|
||||||
|
'(?<ifTrailingString>.*?)' + // Match and Capture all characters (lazy), can be empty
|
||||||
|
'(?=' +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
')'; // Looks ahead but does not capture the next logic operator
|
||||||
|
const matchElseOperator =
|
||||||
|
'(?<isElseStatement>{else})' + // Match & Capture {else}
|
||||||
|
'(?<elseTrailingString>.*?)' + // Match & Capture all characters (lazy), can be empty
|
||||||
|
'(?=' +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
')'; // Looks ahead but does not capture the next logic operator
|
||||||
|
const matchEndOperator =
|
||||||
|
'(?<isEndStatement>{end})' + // Match & Capture {end}
|
||||||
|
'(?<endTrailingString>.*?)' + // Match & Capture all chracters (lazy), can be empty
|
||||||
|
'(?=' +
|
||||||
|
anyLogicOperatorNonCapture +
|
||||||
|
'|$)'; // Looks ahead but does not capture the next logic operator
|
||||||
|
// Combine all matching patterns, separated by 'or' pipes
|
||||||
|
return new RegExp(
|
||||||
|
matchStartOfString +
|
||||||
|
'|' +
|
||||||
|
matchIfOperator +
|
||||||
|
'|' +
|
||||||
|
matchElseOperator +
|
||||||
|
'|' +
|
||||||
|
matchEndOperator,
|
||||||
|
'g'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// End of If Statement Processing Logic //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
|
||||||
export function setupModalLinks(context) {
|
export function setupModalLinks(context) {
|
||||||
context.$nextTick(() => {
|
context.$nextTick(() => {
|
||||||
const elements = document.getElementsByClassName("modal-text")
|
const elements = document.getElementsByClassName("modal-text")
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ const ProviderPreferenceMockData = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmsWidgetName === 'StateSpecificSteeringText') {
|
||||||
|
if (fieldName === 'BodyText') {
|
||||||
|
return "{if:custom:OH}ohioText{else}{if:custom:CA}cali's Text{end}{end}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cmsWidgetName === 'ProviderPreference') {
|
if (cmsWidgetName === 'ProviderPreference') {
|
||||||
if (fieldName === 'QuestionText') {
|
if (fieldName === 'QuestionText') {
|
||||||
return ProviderPreferenceMockData.QuestionText;
|
return ProviderPreferenceMockData.QuestionText;
|
||||||
|
|
@ -106,7 +112,41 @@ const ProviderPreferenceMockData = {
|
||||||
return { mockRoute, mockRouter, wrapper };
|
return { mockRoute, mockRouter, wrapper };
|
||||||
}
|
}
|
||||||
describe('provider-preference.vue', () => {
|
describe('provider-preference.vue', () => {
|
||||||
test('"Continue" button is disabled when no Shop Location is selected.', () => {
|
|
||||||
|
test("getStateSpecificText returns true when values match", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
useMainStore().order.customer.address.state = "ohio";
|
||||||
|
const actual = wrapper.vm.getStateSpecificText("Ohio")
|
||||||
|
|
||||||
|
expect(actual).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getStateSpecificText returns false when values do not match", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
useMainStore().order.customer.address.state = "delaware";
|
||||||
|
const actual = wrapper.vm.getStateSpecificText("Ohio")
|
||||||
|
|
||||||
|
expect(actual).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getStateSpecificText should return correct value for state", () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
useMainStore().order.customer.address.state = "ca";
|
||||||
|
|
||||||
|
let actual = wrapper.vm.steeringModalHeader;
|
||||||
|
|
||||||
|
expect(actual).toBe("cali's Text");
|
||||||
|
|
||||||
|
useMainStore().order.customer.address.state = "oH";
|
||||||
|
|
||||||
|
actual = wrapper.vm.steeringModalHeader;
|
||||||
|
|
||||||
|
expect(actual).toBe("ohioText");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('"Continue" button is disabled when no Shop Location is selected.', () => {
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');
|
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="select-car-form rounded">
|
<div class="select-car-form rounded">
|
||||||
<div v-html="ProviderPreferenceHeaderText" class="text-center mb-5 modal-link" ></div>
|
<div v-html="ProviderPreferenceHeaderText" class="text-center mb-5 modal-link" ></div>
|
||||||
|
<a modalTarget="SteeringModal" class="modal-text" href="#!" aria-label="Modal window">Wow!!</a>
|
||||||
<div
|
<div
|
||||||
v-html="ProviderPreferenceBodyText"
|
v-html="ProviderPreferenceBodyText"
|
||||||
class="mt-0 body-text"
|
class="mt-0 body-text"
|
||||||
|
|
@ -41,16 +42,24 @@
|
||||||
<contentGroupModal cmsWidgetName="ShopPreferenceDrawer"
|
<contentGroupModal cmsWidgetName="ShopPreferenceDrawer"
|
||||||
ref="ShopPreferenceDrawer"
|
ref="ShopPreferenceDrawer"
|
||||||
/>
|
/>
|
||||||
|
<modal
|
||||||
|
ref="SteeringModal"
|
||||||
|
modalId="SteeringModal"
|
||||||
|
:footerButtonText="steeringModalFooter">
|
||||||
|
<h1>{{steeringModalHeader}}</h1>
|
||||||
|
<div>{{steeringModalBody}}</div>
|
||||||
|
</modal>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
// Import Supporting Files
|
// Import Supporting Files
|
||||||
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper';
|
import { fetchCmsContentForPage, setupModalLinks, processIfStatements } from '@/helpers/cms-content-helper';
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { required } from "@/helpers/validation-rules";
|
import { required } from "@/helpers/validation-rules";
|
||||||
import { errorMessages } from "@/constants/error-messages";
|
import { errorMessages } from "@/constants/error-messages";
|
||||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal"
|
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal";
|
||||||
|
import modal from "@/digital-components/modal/modal.vue"
|
||||||
|
|
||||||
// Import Component
|
// Import Component
|
||||||
import baseFormMixin from "@/mixins/base-form-mixin";
|
import baseFormMixin from "@/mixins/base-form-mixin";
|
||||||
|
|
@ -71,7 +80,8 @@ export default {
|
||||||
siteSubHeader,
|
siteSubHeader,
|
||||||
Form,
|
Form,
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
contentGroupModal
|
contentGroupModal,
|
||||||
|
modal
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -108,8 +118,22 @@ export default {
|
||||||
answersFromCms() {
|
answersFromCms() {
|
||||||
return this.getCmsContent("ServiceLocationQuestion", "Answers");
|
return this.getCmsContent("ServiceLocationQuestion", "Answers");
|
||||||
},
|
},
|
||||||
|
steeringModalHeader() {
|
||||||
|
const bodyText = this.getCmsContent("StateSpecificSteeringText", "BodyText");
|
||||||
|
return processIfStatements(bodyText, "custom", this.getStateSpecificText);
|
||||||
|
},
|
||||||
|
steeringModalBody() {
|
||||||
|
const bodyText = this.getCmsContent("StateSpecificSteeringText", "BodyText2");
|
||||||
|
return processIfStatements(bodyText, "custom", this.getStateSpecificText);
|
||||||
|
},
|
||||||
|
steeringModalFooter() {
|
||||||
|
return this.getCmsContent("StateSpecificSteeringText", "FooterText");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getStateSpecificText(value) {
|
||||||
|
return this.mainStore.order.customer.address.state?.toLowerCase() == value?.toLowerCase();
|
||||||
|
},
|
||||||
arePagePrerequisiteValid() {
|
arePagePrerequisiteValid() {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue