added modal for state specific steering text, code to process conditional logic and custom logic to display full state name.

This commit is contained in:
Jason Wheeler 2023-04-19 15:26:34 -04:00
parent 2e27b1fc44
commit 03cc9e47bb
4 changed files with 422 additions and 50 deletions

View file

@ -177,11 +177,11 @@ function mapStringToModal(str) {
let linkToReplace = str.substring(startIndex, str.length);
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf("}") + 1);
let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1)
let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1);
let splitParams = params.split(",");
let bodyText = '<a modalTarget="' + splitParams[0] + '" class="modal-text" aria-label="Modal window">' + splitParams[1] + '</a>'
let returnVal = str.replace(linkToReplace, bodyText)
let bodyText = '<a modalTarget="' + splitParams[0] + '" class="modal-text" aria-label="Modal window">' + splitParams[1] + '</a>';
let returnVal = str.replace(linkToReplace, bodyText);
if (returnVal.includes(dynamicStrings.MODAL_LINK)) {
returnVal = mapStringToModal(returnVal);

View file

@ -0,0 +1,127 @@
import { mount } from "@vue/test-utils"
import servicePackageRadio from "./service-package-radio"
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin"
describe("service-package-radio.vue", () => {
it("Should include buttonLabel in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]))
})
it("Should include buttonLabelAuxillaryCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelAuxillaryCopy"]))
})
it("Should include buttonLabelSubCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]))
})
it("Should include buttonFooterCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const outputHtml = wrapper.html()
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"]))
})
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a dashed (x5) buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
expect(results.length).toBe(5)
})
it("Should get strings from getArrayOfListItemsFromRawCmsCopy without dashes when provided with a dashed buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
const fileredResults = results.filter((result) => {
return result.includes(" -");
})
expect(fileredResults.length).toBe(0)
})
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total dashes, but one is empty ", async () => {
// Arrange
const moddedProps = mockProps
moddedProps["buttonBodyCopy"] = "- buttonBodyCopy test copy - 2 - 3 - 4 - 5 -"
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps,
},
})
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy)
// Assert
expect(results.length).toBe(5)
})
})
const mockProps = {
buttonLabel: 'buttonLabel test copy',
buttonLabelAuxillaryCopy: 'buttonLabelAuxillaryCopy test copy',
buttonLabelSubCopy: 'buttonLabelSubCopy test copy',
buttonBodyCopy: '- buttonBodyCopy test copy - 2 - 3 - 4 - 5',
buttonFooterCopy: 'buttonFooterCopy test copy'
}
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(servicePackageRadio, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin]
})
return { wrapper }
}

View file

@ -0,0 +1,265 @@
<template>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div class="package-label mb-4"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxiliaryCopy"></span>
</p>
<p class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
<div>
<ul >
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem" class="mb-0">
<!-- no textLink -->
<span v-if="!doesCopyContainTextLink(listItem)"
v-html="listItem"></span>
<!-- with textLink -->
<template v-else
v-for="copy in splitCopyOnCMSPlaceHolder(listItem)"
:key="copy">
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span>
<span v-else>
<textLink linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
@click-event="
$emit('buttonEvent', {
eventName: 'openModal',
args: getRouterLinkRouteFromCopy(copy),
})
"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
aria-label="Modal window" />
</span>
</template>
</li>
</ul>
<!-- End of body text parsing -->
<!--ms-n6-->
<div class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-if="this.buttonFooterCopy"
v-html="this.buttonFooterCopy"></div>
</div>
</div>
</div>
</baseInputButton>
</template>
<script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button';
import textLink from '@/ux-components/text-link/text-link';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import {
getRouterLinkDisplayTextFromCopy,
getRouterLinkRouteFromCopy,
splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink,
} from '@/helpers/cms-content-helper';
export default {
name: 'servicePackageRadio',
mixins: [inputButtonWrapperMixin],
components: {
baseInputButton,
textLink,
},
computed: {
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
},
},
methods: {
getRouterLinkDisplayTextFromCopy,
getRouterLinkRouteFromCopy,
splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink,
stripUlTagFromCopy(copy) {
return copy;
},
getArrayOfListItemsFromRawCmsCopy(copy) {
if (copy) {
return copy
.split('- ') //At some point we'll want a better delimiter
.filter((lineItem) => lineItem);
}
}
},
};
</script>
<style lang="scss" scoped>
.ml-n4 {
margin-left: -$spacer * 2;
}
.mr-3 {
margin-right: $spacer * 1.5;
}
.package-main {
.package-wrapper {
margin: 0.5rem 0;
label {
display: block;
}
input[type="radio"] {
opacity: 0;
position: absolute;
left: -9999px;
+ .package-label {
display: flex;
align-items: flex-start;
position: relative;
cursor: pointer;
width: 100%;
padding: 1rem;
border: 1px solid $gray-300;
box-shadow: 0px 4px 8px -4px rgba(0, 0, 0, 0.15), 0px 4px 24px -8px rgba(0, 0, 0, 0.2);
border-radius: 0.5rem;
overflow: hidden;
min-height: 60px;
&.has-subheader {
min-height: 80px;
}
&:before {
content: "";
position: relative;
top: 5px;
margin-right: 1rem;
border-radius: 50%;
border: 1px solid $gray-500;
width: 16px;
height: 16px;
min-width: 16px;
}
&:after {
content: "";
position: absolute;
left: 19px;
top: 24px;
border-radius: 50%;
width: 10px;
height: 10px;
min-width: 10px;
}
}
&:hover {
+ .package-label {
&:before {
border: 1px solid #8e9292;
box-shadow: 0px 0px 0px 4px #9fcee6, 0px 1px 4px rgba(0, 0, 0, 0.2);
}
}
}
&:checked {
+ .package-label {
.package-specs {
max-height: 1000px;
}
}
+ .package-label {
.package-specs {
.hide-when-closed {
display: block;
}
}
}
+ .package-label {
background-color: $blue-100;
border: 1px solid $blue;
max-height: 500px;
}
+ .package-label {
&:before {
box-shadow: 0px 0px 0px 1px $blue;
}
}
+ .package-label {
&:after {
background: $blue;
}
}
}
&:focus {
+ .package-label {
&:before {
border: 2px solid $blue;
}
}
}
}
.package-footer {
color: $red;
}
.package-specs {
display: flex;
flex-direction: column;
width: 100%;
max-height: 1000px;
transition: all 0.5s ease;
p {
font-weight: 500;
display: flex;
justify-content: space-between;
span {
&.pricing-info {
color: $green;
font-size: 0.875rem;
}
}
&.sub-label {
color: $green;
text-transform: uppercase;
font-size: 0.75rem;
}
}
ul {
margin: 1rem 0 0 -.6rem;
padding: 0;
li {
margin-bottom: 0.5rem;
font-size: 0.875rem;
line-height: 1.714;
a {
line-height: 1.714;
padding: 0;
}
}
}
@keyframes slideaway {
from {
display: block;
}
to {
transform: translateY(40px);
opacity: 0;
}
}
}
}
}
</style>

View file

@ -4,40 +4,17 @@
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeader" id="sub-header" />
<div class="select-car">
<div class="container-fluid pb-2">
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<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
v-html="ProviderPreferenceBodyText"
class="mt-0 body-text"
></div>
<buttonQuestion
cmsWidgetName="ServiceLocationQuestion"
:questionText="questionText"
:answers="answersFromCms"
buttonTypeString="listButton"
isRequired
v-model="SelectedshopLocation"
validationRules="questions-required" />
<siteFooter
<div>
<siteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction"
ref="siteFooter"
/>
</div>
</div>
</div>
</div>
ref="siteFooter" />
</div>
</div>
</div>
<contentGroupModal cmsWidgetName="ShopPreferenceDrawer"
ref="ShopPreferenceDrawer"
@ -45,9 +22,13 @@
<modal
ref="SteeringModal"
modalId="SteeringModal"
@footer-button-event="closeSteeringModal"
:footerButtonText="steeringModalFooter">
<h1>{{steeringModalHeader}}</h1>
<div>{{steeringModalBody}}</div>
<h5 class="text-center">{{steeringModalHeader}}</h5>
<div class="steeringModalBody">
<div class="mb-4" >{{steeringModalBody}}</div>
<div v-if="steeringModalBody2">{{steeringModalBody2}}</div>
</div>
</modal>
</Form>
</template>
@ -60,6 +41,7 @@ import { errorMessages } from "@/constants/error-messages";
import buttonQuestion from "@/digital-components/button-question/button-question";
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal";
import modal from "@/digital-components/modal/modal.vue"
import { states } from "@/constants/states"
// Import Component
import baseFormMixin from "@/mixins/base-form-mixin";
@ -109,25 +91,23 @@ export default {
ProviderPreferenceHeaderText(){
return this.getCmsContent("ProviderPreference", "HeaderText");
},
ProviderPreferenceBodyText(){
return this.getCmsContent("ProviderPreference", "BodyText");
},
questionText() {
return this.getCmsContent("ServiceLocationQuestion", "QuestionText");
},
answersFromCms() {
return this.getCmsContent("ServiceLocationQuestion", "Answers");
return this.getCmsContent("ProviderPreference", "SubHeaderText");
},
steeringModalHeader() {
const bodyText = this.getCmsContent("StateSpecificSteeringText", "BodyText");
 return processIfStatements(bodyText, "custom", this.getStateSpecificText);
let header = this.getCmsContent("StateSteeringModal", "HeaderText");
return header.replace("{custom:state}", states[this.mainStore.order.customer.address.state])
},
steeringModalBody() {
const bodyText = this.getCmsContent("StateSpecificSteeringText", "BodyText2");
const bodyText = this.getCmsContent("StateSteeringModal", "BodyText");
 return processIfStatements(bodyText, "custom", this.getStateSpecificText);
},
steeringModalBody2() {
const bodyText = this.getCmsContent("StateSteeringModal", "BodyText2");
 return processIfStatements(bodyText, "custom", this.getStateSpecificText);
},
steeringModalFooter() {
return this.getCmsContent("StateSpecificSteeringText", "FooterText");
return this.getCmsContent("StateSteeringModal", "FooterText");
}
},
methods: {
@ -149,10 +129,15 @@ export default {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE, this.$route);
}
},
resetDependentState() {},
closeSteeringModal() {
this.$refs["SteeringModal"].closeModal();
}
},
mounted() {
setupModalLinks(this);
if(this.steeringModalBody) {
this.$refs["SteeringModal"].openModal();
}
}
};
</script>
@ -160,12 +145,6 @@ export default {
#sub-header span{
color: $black;
}
.body-text {
color: $darker-gray;
p, li {
margin-bottom: 0.5rem;
}
}
.modal-link a{
color: $blue-700;
font-size: 14px;
@ -178,4 +157,5 @@ export default {
text-align: left;
}
}
</style>