Merge branch 'develop' into feature/digital/SSR-639

This commit is contained in:
Katie Kroell 2023-08-16 11:29:24 -04:00
commit 3e7d6586f0
12 changed files with 157 additions and 116 deletions

View file

@ -27,8 +27,8 @@
"vue-router": "4.2.4"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
"@pinia/testing": "0.1.2",
"@rushstack/eslint-patch": "^1.3.2",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/user-event": "14.4.3",
"@testing-library/vue": "6.6.1",

View file

@ -33,7 +33,6 @@ const errorMessages = Object.freeze({
POLICY_NUMBER_REQUIRED: 'Please enter your policy number',
PHONE_NUMBER_REQUIRED: 'Please enter phone number',
PHONE_NUMBER_FORMAT: 'Please enter your phone number. The format must be ###-###-####',
PHONE_NUMBER_FORMAT_SHORT: 'Please enter a valid phone number',
POLICY_ZIP_REQUIRED: 'Please enter your policy ZIP',
POLICY_ZIP_FORMAT: 'Please enter a valid ZIP',
LOSS_CAUSE_REQUIRED: 'Please enter loss cause',

View file

@ -13,7 +13,6 @@ const globalRules = Object.freeze({
EMAIL_ADDRESS_FORMAT: 'email-address-format',
PHONE_NUMBER_REQUIRED: 'phone-number-required',
PHONE_NUMBER_FORMAT: 'phone-number-format',
PHONE_NUMBER_FORMAT_SHORT: 'phone-number-format-short',
OPTION_REQUIRED: 'option-required'
});

View file

@ -17,22 +17,14 @@
:aria-required="isRequired"
:validationRules="validationRules"
:placeHolderText="placeHolderText">
<option
v-if="placeHolderText"
value=""
selected>
{{ placeHolderText }}
<option v-if="placeHolderText" value="" selected>
{{ placeHolderText }}
</option>
<option
v-for="(value, name, index) in options"
:key="index"
:value="name">
<option v-for="(value, name, index) in options" :key="index" :value="name">
{{ value }}
</option>
</select>
<div
v-show="errorMessage"
class="row mt-1 form-test-error">
<div v-show="errorMessage" class="row mt-1 form-test-error">
<span role="alert">{{ errorMessage }}</span>
</div>
</div>
@ -65,12 +57,12 @@ export default {
let initialValue;
switch (typeof modelValue) {
case 'number':
initialValue = modelValue;
break;
default:
initialValue = (modelValue && modelValue.length > 0) ? modelValue : '';
break;
case 'number':
initialValue = modelValue;
break;
default:
initialValue = modelValue && modelValue.length > 0 ? modelValue : '';
break;
}
const fieldOptions = {
@ -79,9 +71,11 @@ export default {
initialValue
};
const { errorMessage, handleBlur, handleChange, meta, errors } = useField(props.inputId,
const { errorMessage, handleBlur, handleChange, meta, errors } = useField(
props.inputId,
props.validationRules,
fieldOptions);
fieldOptions
);
return {
errorMessage,
@ -147,8 +141,6 @@ export default {
}
.form-select {
color: $gray-600;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
border: 1px solid $gray-500;
border-radius: 0.5rem;
min-height: 3rem;
&:focus,

View file

@ -322,11 +322,10 @@ input[type='date']::-webkit-calendar-picker-indicator {
margin-bottom: 0.25rem;
}
.form-control {
border: 1px solid $gray-500;
border-radius: 0.5rem;
min-height: 3rem;
max-height: 48px;
padding: 12px 16px;
max-height: 3rem;
padding: 0.75rem 1rem;
&::placeholder {
color: $gray-500;
}

View file

@ -5,28 +5,32 @@ export function fetchCmsContentForPage(issPage) {
const store = useMainStore();
const { clientName } = store.issConfig;
const { accountNumber } = store.issConfig;
const clientOverride = (clientName.length > 0 && accountNumber > 0);
const clientOverride = clientName.length > 0 && accountNumber > 0;
return store.getPageData(issPage)
// Get the base/default page first.
.then((baseResponse) => {
if (!clientOverride) {
// Return the base page if there are no client override.
return processPageData(baseResponse, null);
}
// Else get the client override page.
const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;
return store.getPageData(pageName)
.then((clientResponse) =>
// Process the client override if it exists.
processPageData(baseResponse, clientResponse),
(error) => {
console.error(error);
// Process the just the base if no client override exists.
return (
store
.getPageData(issPage)
// Get the base/default page first.
.then((baseResponse) => {
if (!clientOverride) {
// Return the base page if there are no client override.
return processPageData(baseResponse, null);
});
});
}
// Else get the client override page.
const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;
return store.getPageData(pageName).then(
(clientResponse) =>
// Process the client override if it exists.
processPageData(baseResponse, clientResponse),
(error) => {
console.error(error);
// Process the just the base if no client override exists.
return processPageData(baseResponse, null);
}
);
})
);
}
// Support method for processing the page data from the CMS call.
@ -44,7 +48,7 @@ function processPageData(baseResponse, clientResponse) {
if (!clientResponse?.data?.Result) {
widgets = baseResponse.data.Result;
} else {
// Override any base widgets with the client override widgets if found.
// Override any base widgets with the client override widgets if found.
baseResponse.data.Result.forEach((baseWidget) => {
let found = false;
clientResponse.data.Result.forEach((clientWidget) => {
@ -75,9 +79,8 @@ function processPageData(baseResponse, clientResponse) {
}
widgets.forEach((widget) => {
// Global state value replacement.
const widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model,
widget.Name);
// Global state value replacement.
const widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Name);
// If we already have this widget, push it on the collection
if (widgetWithReplacements.Name in pageDataFromCms) {
@ -85,9 +88,7 @@ function processPageData(baseResponse, clientResponse) {
return;
}
pageDataFromCms[widgetWithReplacements.Name] = [
widgetWithReplacements.Model
];
pageDataFromCms[widgetWithReplacements.Name] = [widgetWithReplacements.Model];
});
Object.keys(pageDataFromCms).forEach((key) => {
@ -108,8 +109,7 @@ function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
};
Object.keys(widgetModel).forEach((key) => {
const modelWithReplacements = processWidgetItemForReplacement(widgetModel,
key);
const modelWithReplacements = processWidgetItemForReplacement(widgetModel, key);
objWithReplacements.Model[key] = modelWithReplacements;
});
@ -123,9 +123,11 @@ function processWidgetItemForReplacement(widgetModel, key) {
// If we have a string, and it needs to be replaced.
if (typeof widgetModel[key] === 'string') {
if (widgetModel[key].includes('{if:')) {
widgetModel[key] = processIfStatements(widgetModel[key],
widgetModel[key] = processIfStatements(
widgetModel[key],
dynamicStrings.GLOBAL_STATE,
getStoreValueFromString);
getStoreValueFromString
);
}
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
@ -141,8 +143,7 @@ function processWidgetItemForReplacement(widgetModel, key) {
}
// If we have an object. array, etc
if (typeof widgetModel[key] === 'object'
&& Object.keys(widgetModel[key]).length) {
if (typeof widgetModel[key] === 'object' && Object.keys(widgetModel[key]).length) {
Object.keys(widgetModel[key]).forEach((item) => {
processWidgetItemForReplacement(widgetModel[key], item);
});
@ -159,11 +160,13 @@ function mapStringToModal(str) {
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
const params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length - 1);
const params = linkToReplace.substring(
dynamicStrings.MODAL_LINK.length + 2,
linkToReplace.length - 1
);
const splitParams = params.split(',');
const bodyText
= `<a href="#" modalTarget="${splitParams[0]}" class="modal-text" aria-label="Modal window">${splitParams[1]}</a>`;
const bodyText = `<a href="#" modalTarget="${splitParams[0]}" class="modal-text" aria-label="Modal window">${splitParams[1]}</a>`;
let returnVal = str.replace(linkToReplace, bodyText);
@ -178,7 +181,10 @@ function mapStringToLink(str) {
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
const params = linkToReplace.substring((dynamicStrings.EXTERNAL_LINK).length + 2, linkToReplace.length - 1);
const params = linkToReplace.substring(
dynamicStrings.EXTERNAL_LINK.length + 2,
linkToReplace.length - 1
);
const splitParams = params.split(',');
const bodyText = `<a href="${splitParams[0]}" class="external-text" target="_blank">${splitParams[1]}</a>`;
@ -196,7 +202,9 @@ function mapStringToState(str) {
// Pull all matches out of the string.
const regexExp = /{([^{}]*?):([^{}]*?)}/g;
const regexMatches = [...str.matchAll(regexExp)];
const globalStateMatches = regexMatches.filter((match) => match[1] === dynamicStrings.GLOBAL_STATE);
const globalStateMatches = regexMatches.filter(
(match) => match[1] === dynamicStrings.GLOBAL_STATE
);
// Our final string value that will be built from the matches.
const stringBuilder = '';
@ -257,14 +265,17 @@ export function processIfStatements(str, ifConditionKeyword, replacePlaceholderC
}
const ifStatementRegexExpression = getIfStatementRegexExpression();
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(ifStatementRegexMatches,
ifConditionKeyword);
executeIfStatementAndSetProcessedStrings(completeIfStatementArray,
replacePlaceholderCallback);
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
ifStatementRegexMatches,
ifConditionKeyword
);
executeIfStatementAndSetProcessedStrings(completeIfStatementArray, replacePlaceholderCallback);
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
return processIfStatements(reconstructedPostProcessedString,
return processIfStatements(
reconstructedPostProcessedString,
ifConditionKeyword,
replacePlaceholderCallback);
replacePlaceholderCallback
);
}
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
@ -357,38 +368,28 @@ function getIfStatementRegexExpression() {
// {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 characters (lazy), can be empty
+ `(?=${
anyLogicOperatorNonCapture
}|$)`; // Looks ahead but does not capture the next logic operator
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 characters (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');
return new RegExp(
`${matchStartOfString}|${matchIfOperator}|${matchElseOperator}|${matchEndOperator}`,
'g'
);
}
/// ///////////////////////////////////////
@ -446,6 +447,16 @@ export function getRouterLinkDisplayTextFromCopy(copy) {
return copy.split(':')[1].split(',')[1];
}
/**
* Returns a router link as an 'a' tag element
* @returns string
*/
export function getRouterLinkHtmlStringFromCopy(copy) {
return `<a href='/?issPage=${getRouterLinkRouteFromCopy(
copy
)}' class='router-link'>${getRouterLinkDisplayTextFromCopy(copy)}</a>`;
}
// Copy returned from the CMS that has newlines will return blocks wrapped in
// <p ... >...</p>
// This function returns an array of each paragraph, works with or without html

View file

@ -45,13 +45,6 @@ function defineGlobalPhoneNumberRules() {
errorMessages.PHONE_NUMBER_FORMAT
)
);
defineRule(
globalRules.PHONE_NUMBER_FORMAT_SHORT,
regex(
/^(\([0-9]{3}\)|[0-9]{3}) *[-.]? *[0-9]{3} *[-.]? *[0-9]{4}$/,
errorMessages.PHONE_NUMBER_FORMAT_SHORT
)
);
}
/**

View file

@ -0,0 +1,8 @@
/**
* Returns string with inline style tag stripped out
* @returns string
*/
export function stripRteStyle(stringWithStyleTag) {
const regexExp = /[\s*]style="(.*?)"/g;
return stringWithStyleTag.replace(regexExp, '');
}

View file

@ -1,6 +1,11 @@
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header';
import { shallowMount } from '@vue/test-utils';
// Mock cms helpers
jest.mock('@/helpers/cms-content-helper', () => ({
doesCopyContainRouterLink: jest.fn()
}));
describe('site sub header', () => {
const subHeaderText = "let's fix your glass";
const mockMixin = {

View file

@ -24,6 +24,15 @@
<script>
import buttonBack from '@/iss-components/site-sub-header/button-back/button-back';
import {
doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getRouterLinkHtmlStringFromCopy
} from '@/helpers/cms-content-helper';
import { stripRteStyle } from '@/helpers/text-helper';
export default {
name: 'site-sub-header',
@ -44,14 +53,26 @@ export default {
return this.getCmsContent(this.cmsWidgetName, this.contentProperty ?? 'SubHeaderText');
},
subText() {
let subText = this.getCmsContent(
let subTextFromCms = this.getCmsContent(
this.cmsWidgetName,
this.subContentProperty ?? 'SecondaryText'
);
if (this.stripRteStyle) {
const regexExp = /[\s*]style="(.*?)"/g;
subText = subText.replace(regexExp, '');
subTextFromCms = stripRteStyle(subTextFromCms);
}
let subText = '';
if (this.doesCopyContainRouterLink(subTextFromCms)) {
splitCopyOnCMSPlaceHolder(subTextFromCms).forEach((sc) => {
if (this.doesCopyContainRouterLink(sc)) {
subText = subText + getRouterLinkHtmlStringFromCopy(sc);
} else {
subText = subText + sc;
}
});
} else {
subText = subTextFromCms;
}
return subText ?? '';
@ -76,6 +97,11 @@ export default {
}
},
methods: {
doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder,
getRouterLinkRouteFromCopy,
getRouterLinkDisplayTextFromCopy,
getRouterLinkHtmlStringFromCopy,
clickEvent() {
this.$emit('click-event');
}

View file

@ -111,14 +111,14 @@ export default {
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT_SHORT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`
}
};
},
computed: {
isNoTpa() {
return true; // not sure what in the data flags this.
return !this.mainStore.issConfig.enableTPAFlow;
}
},
methods: {

View file

@ -72,5 +72,14 @@ body {
font-size: 14px;
line-height: 24px;
font-weight: 500;
}
}
// Set default border for form-control and form select
.form-control, .form-select {
border: 1px solid $gray-500
}
.form-select {
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
}
}