Baseline linting of several additional layout pages.

This commit is contained in:
DavidAtSafelite 2023-07-27 08:09:33 -04:00
parent 8c39458d91
commit 8620d3888b
35 changed files with 1283 additions and 1302 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
'object-curly-newline': ['error', { consistent: true }], 'object-curly-newline': ['error', { consistent: true }],
'function-paren-newline': ['error', 'never'], 'function-paren-newline': ['error', 'never'],
'operator-linebreak': ['error', 'before'], 'operator-linebreak': ['error', 'before'],
'implicit-arrow-linebreak': ['error', 'beside'], 'implicit-arrow-linebreak': ['off'],
'comma-dangle': ['error', 'never'], 'comma-dangle': ['error', 'never'],
indent: ['error', 4], indent: ['error', 4],
'max-len': ['error', { code: 140 }], 'max-len': ['error', { code: 140 }],

View file

@ -19,64 +19,122 @@ jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn() fetchCmsContentForPage: jest.fn()
})); }));
function setupMocks({
const baseStoreGettersPageData = () => { mountOptionsMockData = {
return { router: {
partsOrQuestions: [ navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn()
},
actionList: [
{ {
parts: null, actionName: 'savePartQuestionAnswers',
partQuestions: [ data: {}
{ },
questionSequence: 1, {
questionText: actionName: 'getParts',
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?', data: {}
answers: [
{
answerResult: '',
answerText: 'Yes',
nextQuestionSequence: 2
},
{
answerResult: '',
answerText: 'No',
nextQuestionSequence: 3
}
]
}
],
glassLocation: 'Windshield',
glassName: 'Single',
answerKey: 'Windshield-Single',
answerData: null
} }
] ],
}; route: {
}; query: {
const baseStoreGettersDamage = () => { issPage: 'part-questions'
return { }
partsQuestionAnswers: [ },
{ data() {
glassLocation: 'Windshield', return {
glassName: 'Single', computedSwitcher: [
result: 'FW04848',
answeredQuestions: [
{ {
questionText: glassLocation: 'Windshield',
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?', glassName: 'Single',
selectedAnswerText: 'Yes', answerData: {
questionNum: 1 answerResult: 'FW04848',
}, answeredQuestions: []
{ }
questionText:
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswerText: 'Yes',
questionNum: 2
} }
] ]
};
},
questionsData: {
get() {
return this.computedSwitcher;
},
set(val) {
this.computedSwitcher = val;
} }
] }
}; }
}; }) {
useMainStore().getParts = jest.fn(() => ({
data: {
glassPieceParts: []
}
}));
const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, vehicleQuestionsMixin]
});
mountOptions.attachTo = document.body;
const wrapper = shallowMount(partQuestions, mountOptions);
return { wrapper };
}
const baseStoreGettersPageData = () => ({
partsOrQuestions: [
{
parts: null,
partQuestions: [
{
questionSequence: 1,
questionText:
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
answers: [
{
answerResult: '',
answerText: 'Yes',
nextQuestionSequence: 2
},
{
answerResult: '',
answerText: 'No',
nextQuestionSequence: 3
}
]
}
],
glassLocation: 'Windshield',
glassName: 'Single',
answerKey: 'Windshield-Single',
answerData: null
}
]
});
const baseStoreGettersDamage = () => ({
partsQuestionAnswers: [
{
glassLocation: 'Windshield',
glassName: 'Single',
result: 'FW04848',
answeredQuestions: [
{
questionText:
'Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?',
selectedAnswerText: 'Yes',
questionNum: 1
},
{
questionText:
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswerText: 'Yes',
questionNum: 2
}
]
}
]
});
useMainStore().pageData = baseStoreGettersPageData; useMainStore().pageData = baseStoreGettersPageData;
useMainStore().damage = baseStoreGettersDamage; useMainStore().damage = baseStoreGettersDamage;
@ -99,9 +157,7 @@ describe('partQuestions.vue...', () => {
test('Should return false for valid page requisites if partsOrQuestions in pageData is missing', () => { test('Should return false for valid page requisites if partsOrQuestions in pageData is missing', () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
useMainStore().pageData = jest.fn(() => { useMainStore().pageData = jest.fn(() => undefined);
return undefined;
});
// Act // Act
const result = wrapper.vm.arePagePrerequisitesValid(); const result = wrapper.vm.arePagePrerequisitesValid();
@ -114,11 +170,9 @@ describe('partQuestions.vue...', () => {
test('Should be at least one item in partsOrQuestions', () => { test('Should be at least one item in partsOrQuestions', () => {
// Arrange // Arrange
useMainStore().pageData = jest.fn(() => { useMainStore().pageData = jest.fn(() => ({
return { partsOrQuestions: []
partsOrQuestions: [] }));
};
});
useMainStore().damage = baseStoreGettersDamage; useMainStore().damage = baseStoreGettersDamage;
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -212,13 +266,11 @@ describe('partQuestions.vue...', () => {
] ]
}); });
wrapper.vm.getParts = jest.fn(() => { wrapper.vm.getParts = jest.fn(() => ({
return { data: {
data: { glassPieceParts: []
glassPieceParts: [] }
} }));
};
});
// Act // Act
wrapper.vm.forwardButtonAction(); wrapper.vm.forwardButtonAction();
@ -246,13 +298,11 @@ describe('partQuestions.vue...', () => {
} }
]; ];
useMainStore().getParts = jest.fn(() => { useMainStore().getParts = jest.fn(() => ({
return { data: {
data: { glassPieceParts: []
glassPieceParts: [] }
} }));
};
});
// Act // Act
wrapper.vm.forwardButtonAction(); wrapper.vm.forwardButtonAction();
@ -278,13 +328,11 @@ describe('partQuestions.vue...', () => {
} }
} }
]; ];
useMainStore().getParts = jest.fn(() => { useMainStore().getParts = jest.fn(() => ({
return { data: {
data: { glassPieceParts: []
glassPieceParts: [] }
} }));
};
});
// Act // Act
wrapper.vm.forwardButtonAction(); wrapper.vm.forwardButtonAction();
@ -311,13 +359,11 @@ describe('partQuestions.vue...', () => {
} }
} }
]; ];
useMainStore().getParts = jest.fn(() => { useMainStore().getParts = jest.fn(() => ({
return { data: {
data: { glassPieceParts: []
glassPieceParts: [] }
} }));
};
});
wrapper.vm.navigateForward = jest.fn(); wrapper.vm.navigateForward = jest.fn();
// Act // Act
@ -330,68 +376,3 @@ describe('partQuestions.vue...', () => {
}); });
}); });
}); });
function setupMocks({
mountOptionsMockData = {
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn()
},
actionList: [
{
actionName: 'savePartQuestionAnswers',
data: {}
},
{
actionName: 'getParts',
data: {}
}
],
route: {
query: {
issPage: 'part-questions'
}
},
data() {
return {
computedSwitcher: [
{
glassLocation: 'Windshield',
glassName: 'Single',
answerData: {
answerResult: 'FW04848',
answeredQuestions: []
}
}
]
};
},
questionsData: {
get() {
return this.computedSwitcher;
},
set(val) {
this.computedSwitcher = val;
}
}
}
}) {
useMainStore().getParts = jest.fn(() => {
return {
data: {
glassPieceParts: []
}
};
});
const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, vehicleQuestionsMixin]
});
mountOptions['attachTo'] = document.body;
const wrapper = shallowMount(partQuestions, mountOptions);
return { wrapper };
}

View file

@ -1,16 +1,20 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<questions-page-layout <questions-page-layout
ref="questionsPageLayout" ref="questionsPageLayout"
v-model="selectedAnswers"
:isMetaValid="meta.valid" :isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader" :alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy" :alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="rules.optionRequired" :validationRules="rules.optionRequired"
v-model="selectedAnswers" :index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction" @forwardButtonAction="forwardButtonAction"
@back-click="navigateBack" @back-click="navigateBack" />
:index="currentGlassIndex" />
</Form> </Form>
</template> </template>
@ -30,6 +34,10 @@ import globalRules from '@/constants/global-rules';
export default { export default {
name: 'part-questions', name: 'part-questions',
components: {
Form,
questionsPageLayout
},
mixins: [BaseFormMixin, vehicleQuestionsMixin], mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
@ -80,22 +88,20 @@ export default {
.map((glass, index) => { .map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts // NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.partQuestions; glass.questions = glass.partQuestions;
glass.answerKey = glass.glassLocation + '-' + glass.glassName; glass.answerKey = `${glass.glassLocation}-${glass.glassName}`;
// reset selectedAnswers for this glass // reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = []; this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions); const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
// Set up watch for each set of glass questions // Set up watch for each set of glass questions
this.$watch( this.$watch(`selectedAnswers.${glass.answerKey}`,
'selectedAnswers.' + glass.answerKey,
(newValue) => { (newValue) => {
if (newValue && Object.keys(newValue).length > 0) { if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey); this.handleAnswerUpdates(newValue, glass.answerKey);
} }
}, },
{ deep: true } { deep: true });
);
return updatedGlass; return updatedGlass;
}); });
@ -104,20 +110,18 @@ export default {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
const partQuestionsFromPageData = useMainStore().pageData(issPageValues.PART_QUESTIONS); const partQuestionsFromPageData = useMainStore().pageData(issPageValues.PART_QUESTIONS);
return ( return (
partQuestionsFromPageData && partQuestionsFromPageData
Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0 && Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0
); );
}, },
async forwardButtonAction() { async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => { const questionAnswersArray = this.questionsData.map((glass) => ({
return { glassLocation: glass.glassLocation,
glassLocation: glass.glassLocation, glassName: glass.glassName,
glassName: glass.glassName, result: (glass.answerData && glass.answerData.answerResult) || '',
result: (glass.answerData && glass.answerData.answerResult) || '', answeredQuestions: glass.answerData?.answeredQuestions,
answeredQuestions: glass.answerData?.answeredQuestions, isSuppressedPart: glass.isSuppressedPart
isSuppressedPart: glass.isSuppressedPart }));
};
});
// clear out answerData for future page loads; must occur prior to store save // clear out answerData for future page loads; must occur prior to store save
this.questionsData.forEach((glass) => { this.questionsData.forEach((glass) => {
@ -137,10 +141,6 @@ export default {
// TODO KO delete for quote mvp // TODO KO delete for quote mvp
this.navigateForward(glassPartsForStore, null); this.navigateForward(glassPartsForStore, null);
} }
},
components: {
Form,
questionsPageLayout
} }
}; };
</script> </script>

View file

@ -1,17 +1,20 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" > <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget"/> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 payment"> <div class="container-fluid pb-2 payment">
<p>Placeholder for payment page</p> <p>Placeholder for payment page</p>
<siteFooter <siteFooter
cmsWidgetName="SiteFooterWidget"
ref="siteFooter" ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" @back-clicked="backButtonAction" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -31,14 +34,12 @@ import { useMainStore } from '@/store';
export default { export default {
name: 'payment-page', name: 'payment-page',
components: {
siteHeader,
siteFooter,
Form
},
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
data() {
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -51,11 +52,18 @@ export default {
}]; }];
// use resultMap to populate layout content. // use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
}, },
methods: methods:
{ {
@ -69,17 +77,11 @@ export default {
navigateForward() { navigateForward() {
} }
}, }
components: { };
siteHeader,
siteFooter,
Form,
}
}
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.payment p{ .payment p{
margin-bottom:0.75rem; margin-bottom:0.75rem;
} }
</style> </style>

View file

@ -1,10 +1,9 @@
import policyHolderDetails from '@/layouts/policy-holder-details/policy-holder-details.vue'; import policyHolderDetails from '@/layouts/policy-holder-details/policy-holder-details';
// Supporting files // Supporting files
// Supporting files // Supporting files
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js'; import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios'; import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
@ -19,6 +18,55 @@ jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn() fetchCmsContentForPage: jest.fn()
})); }));
function setupMocks() {
const wrapper = shallowMount(policyHolderDetails,
getMountOptions({
router: {
navigate: jest.fn()
},
mainStore: {
order: {
customer: {
emailAddress: 'test@test.com'
}
}
}
}));
const policies = [{
vehicles: [
{
vin: 'TEST_VIN'
},
{
vin: 'TEST_VIN2'
}
]
}
];
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => Promise.resolve({
data: {
policies
}
}));
const apiResponses = {
policyLookupResponse: {
policies
}
};
settleAllPromises.mockImplementation(() => apiResponses);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => '');
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$refs.siteFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.siteFooter.removeLoader = jest.fn();
wrapper.vm.$refs.siteFooter.disableForwardButton = jest.fn();
wrapper.vm.$refs.siteFooter.enableForwardAction = jest.fn();
return { wrapper };
}
describe('policy-holder-details.vue', () => { describe('policy-holder-details.vue', () => {
test('Should render policy-holder-details sub-components (policy holder first name, last name, street address etc.)', async () => { test('Should render policy-holder-details sub-components (policy holder first name, last name, street address etc.)', async () => {
// Arrange // Arrange
@ -116,65 +164,10 @@ describe('navigation', () => {
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
undefined, undefined,
{}, {},
{}, {},
mockvehicles mockvehicles);
);
}); });
}); });
function setupMocks() {
const wrapper = shallowMount(
policyHolderDetails,
getMountOptions({
router: {
navigate: jest.fn()
},
mainStore: {
order: {
customer: {
emailAddress: 'test@test.com'
}
}
}
})
);
const policies = [{
vehicles: [
{
vin: 'TEST_VIN'
},
{
vin: 'TEST_VIN2'
}
]
}
];
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => {
return Promise.resolve({
data: {
policies:policies
}
})
})
const apiResponses = {
policyLookupResponse:{
policies:policies
}
};
settleAllPromises.mockImplementation(() => apiResponses);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => '');
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$refs.siteFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.siteFooter.removeLoader = jest.fn();
wrapper.vm.$refs.siteFooter.disableForwardButton = jest.fn();
wrapper.vm.$refs.siteFooter.enableForwardAction = jest.fn();
return { wrapper };
}

View file

@ -1,40 +1,49 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" > <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget"/> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 px-5"> <div class="container-fluid pb-2 px-5">
<div class="row mt-4 mb-3"> <div class="row mt-4 mb-3">
<div class="col"> <div class="col">
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" id="sub-header"/> <siteSubHeader
<addressQuestions ref="addressQuestions" v-model="customerQuestions.addressQuestions" includeStreetAddress2="true" id="address-questions-wrapper"/> id="sub-header"
cmsWidgetName="SiteSubHeaderWidget" />
<addressQuestions
id="address-questions-wrapper"
ref="addressQuestions"
v-model="customerQuestions.addressQuestions"
includeStreetAddress2="true" />
<textboxQuestion <textboxQuestion
inputId="firstNameField" ref="policyHolderFirstName"
cmsWidgetName="PolicyholderFirstNameQuestion" v-model="customerQuestions.firstName"
v-model="customerQuestions.firstName" inputId="firstNameField"
isRequired cmsWidgetName="PolicyholderFirstNameQuestion"
ref="policyHolderFirstName" isRequired
disableAutoFill disableAutoFill
:validationRules="rules.firstName" /> :validationRules="rules.firstName" />
<textboxQuestion <textboxQuestion
class="mt-4" ref="policyHolderLastName"
inputId="lastNameField" v-model="customerQuestions.lastName"
cmsWidgetName="PolicyholderLastNameQuestion" class="mt-4"
v-model="customerQuestions.lastName" inputId="lastNameField"
isRequired cmsWidgetName="PolicyholderLastNameQuestion"
ref="policyHolderLastName" isRequired
disableAutoFill disableAutoFill
:validationRules="rules.lastName" /> :validationRules="rules.lastName" />
</div> </div>
</div> </div>
<siteFooter <siteFooter
class="mt-5" ref="siteFooter"
cmsWidgetName="SiteFooterWidget" class="mt-5"
ref="siteFooter" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" @back-clicked="backButtonAction" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -58,21 +67,16 @@ import { useMainStore } from '@/store';
export default { export default {
name: 'policy-holder-details', name: 'policy-holder-details',
components: {
siteHeader,
siteSubHeader,
textboxQuestion,
addressQuestions,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
data() {
return {
customerQuestions: this.getPolicyHolderDetailsFromStore(),
vehiclesFound: [],
rules: {
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED
}
};
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -86,12 +90,34 @@ export default {
]; ];
// use resultMap to populate layout content. // use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
}, },
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
customerQuestions: this.getPolicyHolderDetailsFromStore(),
vehiclesFound: [],
rules: {
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED
}
};
},
computed: {
isCoverageEnabled() {
return this.mainStore.issConfig.isCoverageEnabled;
},
vehiclesCount() {
return this.vehiclesFound.length;
}
},
methods: methods:
{ {
backButtonAction() { backButtonAction() {
@ -105,18 +131,14 @@ export default {
navigateForward() { navigateForward() {
if (this.vehiclesCount > 0) { if (this.vehiclesCount > 0) {
this.$router.navigate( this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
this.$route, this.$route,
{}, {},
{}, {},
this.vehiclesFound this.vehiclesFound);
);
} else { } else {
this.$router.navigate( this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, this.$route);
this.$route
);
} }
}, },
@ -132,29 +154,13 @@ export default {
}, },
firstName: this.mainStore.order.customer.firstName, firstName: this.mainStore.order.customer.firstName,
lastName: this.mainStore.order.customer.lastName lastName: this.mainStore.order.customer.lastName
} };
} }
},
computed: {
isCoverageEnabled() {
return this.mainStore.issConfig.isCoverageEnabled;
},
vehiclesCount() {
return this.vehiclesFound.length;
}
},
components: {
siteHeader,
siteSubHeader,
textboxQuestion,
addressQuestions,
siteFooter,
Form
} }
} };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
#sub-header p { #sub-header p {
font-size: 16px; font-size: 16px;
line-height: 26px; line-height: 26px;

View file

@ -1,9 +1,15 @@
import policyVehiclesQuestion from '@/layouts/policy-vehicles/policy-vehicles-question/policy-vehicles-question.vue'; import policyVehiclesQuestion from '@/layouts/policy-vehicles/policy-vehicles-question/policy-vehicles-question';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
const mockMixin = {
methods: {
getCmsContent: jest.fn(() => ['vehicle not listed'])
}
};
describe('policy-vehicles-question.vue', () => { describe('policy-vehicles-question.vue', () => {
test('Selected vehicle is emitted upon selection', async () => { test('Selected vehicle is emitted upon selection', async () => {
// Arrange // Arrange
const vehicleToSelect = 'Test1'; const vehicleToSelect = 'Test1';
const wrapper = shallowMount(policyVehiclesQuestion, { const wrapper = shallowMount(policyVehiclesQuestion, {
mixins: [mockMixin], mixins: [mockMixin],
@ -22,11 +28,3 @@ describe('policy-vehicles-question.vue', () => {
]); ]);
}); });
}); });
const mockMixin = {
methods: {
getCmsContent: jest.fn(() => {
return ['vehicle not listed'];
})
}
};

View file

@ -10,7 +10,7 @@
:validationRules="validationRules" /> :validationRules="validationRules" />
</template> </template>
<script> <script>
import buttonQuestion from '@/digital-components/button-question/button-question.vue'; import buttonQuestion from '@/digital-components/button-question/button-question';
export default ({ export default ({
name: 'policy-vehicles-question', name: 'policy-vehicles-question',

View file

@ -1,4 +1,4 @@
import policyVehicles from '@/layouts/policy-vehicles/policy-vehicles.vue'; import policyVehicles from '@/layouts/policy-vehicles/policy-vehicles';
import { settleAllPromises } from '@/helpers/layout-helper'; import { settleAllPromises } from '@/helpers/layout-helper';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper'; import { getMountOptions } from '@/helpers/unit-test-helper';
@ -9,8 +9,8 @@ import baseMixin from '@/mixins/base-mixin';
import { getRandomString, getRandomInt } from '@/helpers/data-generation'; import { getRandomString, getRandomInt } from '@/helpers/data-generation';
import { endorsementOptions } from '@/constants/endorsement-options'; import { endorsementOptions } from '@/constants/endorsement-options';
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import { issPageValues } from "@/router/router-constants/issPage-values"; import { issPageValues } from '@/router/router-constants/issPage-values';
import { vehicleSelectionOptions } from "@/constants/vehicle-selection-options"; import { vehicleSelectionOptions } from '@/constants/vehicle-selection-options';
// Mock fetchCmsContentForPage // Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({ jest.mock('@/helpers/cms-content-helper', () => ({
@ -27,6 +27,54 @@ jest.mock('@/helpers/layout-helper.js', () => ({
settleAllPromises: jest.fn() settleAllPromises: jest.fn()
})); }));
const mockMixin = {
methods: {
getCmsContent: jest.fn()
},
computed: {
dynamicStrings() {
return { ROUTER_LINK: 'routerLink:' };
}
}
};
function setupMocks() {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
mixins: [mockMixin],
global: {
mocks: {
$route: {
params: {
id: 1
}
},
$router: {
navigate: jest.fn()
}
},
plugins: [createTestingPinia()]
}
});
const apiResponses = {
cmsContent: {}
};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const wrapper = shallowMount(policyVehicles, mountOptions);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => '');
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.$refs.siteFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.siteFooter.removeLoader = jest.fn();
return { wrapper };
}
describe('policy-vehicles.vue', () => { describe('policy-vehicles.vue', () => {
test('Should navigate to CLICKED_BACK if backButtonAction is run', async () => { test('Should navigate to CLICKED_BACK if backButtonAction is run', async () => {
// Arrange // Arrange
@ -48,7 +96,7 @@ describe('policy-vehicles.vue', () => {
await wrapper.setData({ await wrapper.setData({
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ vin: vin }, { vin }
], ],
bailout: false bailout: false
}); });
@ -56,19 +104,19 @@ describe('policy-vehicles.vue', () => {
const year = getRandomInt(1998, 2023); const year = getRandomInt(1998, 2023);
const lookupVehicleResponse = { const lookupVehicleResponse = {
data: { data: {
year: year, year
} }
}; };
const store = useMainStore(); const store = useMainStore();
store.lookupVehicleByVin.mockReturnValue(Promise.resolve(lookupVehicleResponse)); store.lookupVehicleByVin.mockReturnValue(Promise.resolve(lookupVehicleResponse));
const expectedInput = { const expectedInput = {
year: year, year,
vin: vin, vin,
noCoverage: true, noCoverage: true,
deductible: 0, deductible: 0,
repairWaived: false repairWaived: false
} };
// Act // Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -76,12 +124,10 @@ describe('policy-vehicles.vue', () => {
// Assert // Assert
expect(wrapper.vm.bailout).toBeFalsy(); expect(wrapper.vm.bailout).toBeFalsy();
expect(store.updateVehicle).toHaveBeenCalledWith(expectedInput); expect(store.updateVehicle).toHaveBeenCalledWith(expectedInput);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
undefined, undefined,
{}, {},
{} {});
);
}); });
test('Error in lookupVehicleByVin call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario.', async () => { test('Error in lookupVehicleByVin call => bailout true and navigate forward with CLICKED_FORWARD_WITH_BAILOUT scenario.', async () => {
@ -102,12 +148,10 @@ describe('policy-vehicles.vue', () => {
// Assert // Assert
expect(wrapper.vm.bailout).toBeTruthy(); expect(wrapper.vm.bailout).toBeTruthy();
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
undefined, undefined,
{}, {},
{} {});
);
}); });
test('vehicle not listed => navigate forward with CLICKED_FORWARD_NON_LISTED_VEHICLE scenario.', async () => { test('vehicle not listed => navigate forward with CLICKED_FORWARD_NON_LISTED_VEHICLE scenario.', async () => {
@ -122,12 +166,10 @@ describe('policy-vehicles.vue', () => {
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
undefined, undefined,
{}, {},
{} {});
);
}); });
}); });
@ -209,7 +251,7 @@ describe('policy-vehicles.vue', () => {
vin: otherVin vin: otherVin
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues); const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -225,11 +267,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ {
vin: vin, vin,
coverages: [] coverages: []
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues); const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -247,7 +289,7 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ {
vin: vin, vin,
coverages: [ coverages: [
{ {
deductible: firstDeductible deductible: firstDeductible
@ -258,7 +300,7 @@ describe('policy-vehicles.vue', () => {
] ]
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues); const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -280,7 +322,7 @@ describe('policy-vehicles.vue', () => {
vin: otherVin vin: otherVin
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues); const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -296,11 +338,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ {
vin: vin, vin,
endorsements: [] endorsements: []
} }
], ]
} };
// Act // Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues); const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -316,11 +358,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ {
vin: vin, vin,
endorsements: [ endorsementOptions.EDUCATOR, endorsementOptions.PARKING_GUARD ] endorsements: [endorsementOptions.EDUCATOR, endorsementOptions.PARKING_GUARD]
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues); const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -336,7 +378,7 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin, selectedVehicleVin: vin,
policyVehicles: [ policyVehicles: [
{ {
vin: vin, vin,
endorsements: [ endorsements: [
endorsementOptions.EDUCATOR, endorsementOptions.EDUCATOR,
endorsementOptions.REPAIR_WAIVED, endorsementOptions.REPAIR_WAIVED,
@ -344,7 +386,7 @@ describe('policy-vehicles.vue', () => {
] ]
} }
] ]
} };
// Act // Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues); const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -359,7 +401,7 @@ describe('policy-vehicles.vue', () => {
const vin = getRandomString(17, 17); const vin = getRandomString(17, 17);
useMainStore().applicationUser = { useMainStore().applicationUser = {
pageData: { pageData: {
[issPageValues.POLICY_VEHICLES]: [{ vin: vin }], [issPageValues.POLICY_VEHICLES]: [{ vin }]
} }
}; };
@ -372,51 +414,3 @@ describe('policy-vehicles.vue', () => {
expect(wrapper.vm.selectedVehicleVin).toBe(vin); expect(wrapper.vm.selectedVehicleVin).toBe(vin);
}); });
}); });
const mockMixin = {
methods: {
getCmsContent: jest.fn()
},
computed: {
dynamicStrings() {
return { ROUTER_LINK: 'routerLink:' };
}
}
};
function setupMocks() {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
mixins: [mockMixin],
global: {
mocks: {
$route: {
params: {
id: 1
}
},
$router: {
navigate: jest.fn()
}
},
plugins: [createTestingPinia()]
}
});
const apiResponses = {
cmsContent: {}
};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const wrapper = shallowMount(policyVehicles, mountOptions);
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => '');
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.$refs.siteFooter.updateButtonText = jest.fn();
wrapper.vm.$refs.siteFooter.removeLoader = jest.fn();
return { wrapper };
}

View file

@ -1,113 +1,6 @@
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import providerPrefRadio from './provider-pref-radio';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin'; import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import providerPrefRadio from './provider-pref-radio';
describe('provider-pref-radio.vue', () => {
it('Should include buttonLabel in html', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const outputHtml = wrapper.html();
console.log(outputHtml);
// 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 = { const mockProps = {
buttonLabel: 'buttonLabel test copy', buttonLabel: 'buttonLabel test copy',
@ -125,3 +18,109 @@ function setupMocks({ mountOptionsMockData = {} }) {
return { wrapper }; return { wrapper };
} }
describe('provider-pref-radio.vue', () => {
it('Should include buttonLabel in html', async () => {
// Arrange
const { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps
}
});
// Act
const outputHtml = wrapper.html();
console.log(outputHtml);
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps.buttonLabel));
});
it('Should include buttonLabelAuxillaryCopy in html', async () => {
// Arrange
const { 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
const { 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
const { 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
const { 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
const { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
const fileredResults = results.filter((result) => 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 -';
const { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
});

View file

@ -1,19 +1,28 @@
<template> <template>
<baseInputButton v-bind="$props" v-model="selectedValue"> <baseInputButton
<div class="option-label" v-bind="$props"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']" v-model="selectedValue">
for="testradio"> <div
class="option-label"
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="option-specs"> <div class="option-specs">
<p class="m-0"> <p class="m-0">
<span v-html="this.buttonLabel"></span> <span v-html="buttonLabel"></span>
<span class="option-info" v-html="this.buttonAuxiliaryCopy"></span> <span
class="option-info"
v-html="buttonAuxiliaryCopy"></span>
</p> </p>
<p class="sub-label m-0" <p
v-if="this.buttonLabelSubCopy" v-if="buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p> class="sub-label m-0"
v-html="buttonLabelSubCopy"></p>
<div> <div>
<ul > <ul>
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem" class="mb-0"> <li
v-for="listItem in arrayOfListItemsFromBodyText"
:key="listItem"
class="mb-0">
<!-- no textLink --> <!-- no textLink -->
<span v-html="listItem"></span> <span v-html="listItem"></span>
</li> </li>
@ -25,17 +34,15 @@
</template> </template>
<script> <script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button'; 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 inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
export default { export default {
name: 'providerPrefRadio', name: 'provider-pref-radio',
mixins: [inputButtonWrapperMixin],
components: { components: {
baseInputButton, baseInputButton
textLink
}, },
mixins: [inputButtonWrapperMixin],
computed: { computed: {
arrayOfListItemsFromBodyText() { arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy); return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
@ -48,6 +55,7 @@ export default {
.split('- ') // At some point we'll want a better delimiter .split('- ') // At some point we'll want a better delimiter
.filter((lineItem) => lineItem); .filter((lineItem) => lineItem);
} }
return null;
} }
} }
}; };
@ -217,8 +225,8 @@ export default {
} }
} }
} }
} }
} }
} }
</style> </style>

View file

@ -1,8 +1,8 @@
import ProviderPreference from '@/layouts/provider-preference/provider-preference.vue' import ProviderPreference from '@/layouts/provider-preference/provider-preference';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js'; import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios'; import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
@ -18,6 +18,29 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn() setupModalLinks: jest.fn()
})); }));
function setupMocks(mockApiResponses) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
route: 'provider-preference'
});
const wrapper = shallowMount(ProviderPreference,
mountOptions);
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => Promise.resolve({
data: {}
}));
const apiResponses = mockApiResponses;
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => { });
return { wrapper };
}
describe('provider-preference.vue', () => { describe('provider-preference.vue', () => {
test('Should navigate to safelite flow when safelite selected', () => { test('Should navigate to safelite flow when safelite selected', () => {
// Arrange // Arrange
@ -57,30 +80,3 @@ describe('provider-preference.vue', () => {
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference'); expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference');
}); });
}); });
function setupMocks(mockApiResponses) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
route: 'provider-preference'
});
const wrapper = shallowMount(
ProviderPreference,
mountOptions
);
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => {
return Promise.resolve({
data: {}
})
})
const apiResponses = mockApiResponses;
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation (() => {});
return { wrapper };
}

View file

@ -1,52 +1,60 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm"> <Form
ref="theForm"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" /> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeader" id="sub-header" class="mb-5 mt-4" /> <siteSubHeader
id="sub-header"
<buttonQuestion ref="buttonQuestion" cmsWidgetName="SiteSubHeader"
class="mb-5 mt-4" />
<buttonQuestion
ref="buttonQuestion"
v-model="selectedProvider"
questionText="Select an option:" questionText="Select an option:"
:answers="prefAnswers" :answers="prefAnswers"
groupName="prefQuestions" groupName="prefQuestions"
buttonTypeString="providerPrefRadio" buttonTypeString="providerPrefRadio"
v-model="selectedProvider"
:validationRules="rules.optionRequired" :validationRules="rules.optionRequired"
isRequired isRequired
class="mx-5" /> class="mx-5" />
<div class="px-5"> <div class="px-5">
<siteFooter <siteFooter
ref="siteFooter"
class="mt-5" class="mt-5"
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled" :isForwardActionDisabled="isForwardActionDisabled"
@backClicked="backButtonAction" @backClicked="backButtonAction"
@forwardClicked="forwardButtonAction" @forwardClicked="forwardButtonAction" />
ref="siteFooter" />
</div> </div>
</div> </div>
</div> </div>
<recalModal ref="recalModal" cmsWidgetName="RecalModal" /> <recalModal
<steeringModal cmsWidgetName="StateSteeringModal" ref="StateSteeringModal" /> ref="recalModal"
cmsWidgetName="RecalModal" />
<steeringModal
ref="StateSteeringModal"
cmsWidgetName="StateSteeringModal" />
<shopPreferenceModal <shopPreferenceModal
cmsWidgetName="ShopPreferenceDrawer"
ref="ShopPreferenceDrawer" ref="ShopPreferenceDrawer"
@openSteering="openStateSteeringModal" cmsWidgetName="ShopPreferenceDrawer"
:showSteeringLink="showSteeringLink"/> :showSteeringLink="showSteeringLink"
<tpaRecalModal cmsWidgetName="TPARecalModal" @openSteering="openStateSteeringModal" />
<tpaRecalModal
ref="TPARecalModal" ref="TPARecalModal"
cmsWidgetName="TPARecalModal"
:ackError="ackError" :ackError="ackError"
@buttonClick="navigateWithTPAAck" @buttonClick="navigateWithTPAAck" />
/>
</Form> </Form>
</template> </template>
<script> <script>
// Import Supporting Files // Import Supporting Files
import { fetchCmsContentForPage, setupModalLinks, processIfStatements } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper';
import { settleAllPromises } from '@/helpers/layout-helper'; import { settleAllPromises } from '@/helpers/layout-helper';
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 { issPageValues } from '@/router/router-constants/issPage-values'; import { issPageValues } from '@/router/router-constants/issPage-values';
// Import Component // Import Component
@ -55,42 +63,30 @@ import { Form } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer'; import siteFooter from '@/iss-components/site-footer/site-footer';
import siteHeader from '@/iss-components/site-header/site-header'; import siteHeader from '@/iss-components/site-header/site-header';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header';
import providerPrefRadio from './provider-pref-radio/provider-pref-radio';
import recalModal from '@/layouts/coverage-statement/recal-modal/recal-modal'; import recalModal from '@/layouts/coverage-statement/recal-modal/recal-modal';
import steeringModal from '@/layouts/provider-preference/steering-modal/steering-modal'; import steeringModal from '@/layouts/provider-preference/steering-modal/steering-modal';
import shopPreferenceModal from '@/layouts/provider-preference/shop-preference-modal/shop-preference-modal'; import shopPreferenceModal from '@/layouts/provider-preference/shop-preference-modal/shop-preference-modal';
import tpaRecalModal from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal.vue' import tpaRecalModal from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal';
import globalRules from '@/constants/global-rules'; import globalRules from '@/constants/global-rules';
const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' }; const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' };
export default { export default {
name: 'provider-preference', name: 'provider-preference',
mixins: [baseFormMixin],
components: { components: {
siteFooter, siteFooter,
siteHeader, siteHeader,
siteSubHeader, siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form, Form,
buttonQuestion, buttonQuestion,
contentGroupModal,
providerPrefRadio,
recalModal, recalModal,
steeringModal, steeringModal,
shopPreferenceModal, shopPreferenceModal,
tpaRecalModal tpaRecalModal
}, },
data() { mixins: [baseFormMixin],
return {
selectedProvider: null,
showSteeringLink: false,
tpaAcknowledgement: false,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
},
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results // Settle promises and get results
@ -105,11 +101,21 @@ export default {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.showSteeringLink = !!vm.$refs.StateSteeringModal.ModalBodyText; vm.showSteeringLink = !!vm.$refs.StateSteeringModal.ModalBodyText;
if(vm.showSteeringLink) { if (vm.showSteeringLink) {
vm.$refs.StateSteeringModal.openModal(); vm.$refs.StateSteeringModal.openModal();
} }
}); });
}, },
data() {
return {
selectedProvider: null,
showSteeringLink: false,
tpaAcknowledgement: false,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
},
computed: { computed: {
isForwardActionDisabled() { isForwardActionDisabled() {
return this.selectedProvider === null; return this.selectedProvider === null;
@ -140,13 +146,19 @@ export default {
return errorMessages.ACKNOWLEDGEMENT_REQUIRED; return errorMessages.ACKNOWLEDGEMENT_REQUIRED;
} }
}, },
mounted() {
setupModalLinks(this);
const pageData = this.mainStore.pageData(issPageValues.PROVIDER_PREFERENCE);
this.selectedProvider = pageData?.selectedProvider ? pageData.selectedProvider : null;
this.tpaAcknowledgement = pageData?.tpaAcknowledgement ? pageData.tpaAcknowledgement : false;
},
methods: { methods: {
getHeaderTextFromCms(cmsWidgetName) { getHeaderTextFromCms(cmsWidgetName) {
const headerText = this.getCmsContent(cmsWidgetName, 'HeaderText'); const headerText = this.getCmsContent(cmsWidgetName, 'HeaderText');
return headerText?.replace('{custom:SafeliteLogo}', "<span class='safeliteLogo'></span>"); return headerText?.replace('{custom:SafeliteLogo}', "<span class='safeliteLogo'></span>");
}, },
getSubheaderTextFromCms(cmsWidgetName) { getSubheaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'SubheaderText'); return this. getCmsContent(cmsWidgetName, 'SubheaderText');
}, },
getBodyTextFromCms(cmsWidgetName) { getBodyTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'BodyText'); return this.getCmsContent(cmsWidgetName, 'BodyText');
@ -161,7 +173,10 @@ export default {
this.$router.navigate(scenario, this.$route); this.$router.navigate(scenario, this.$route);
}, },
navigateWithTPAAck() { navigateWithTPAAck() {
this.mainStore.saveProviderPreferenceData({selectedProvider: this.selectedProvider, tpaAcknowledgement: this.tpaAcknowledgement}); this.mainStore.saveProviderPreferenceData({
selectedProvider: this.selectedProvider,
tpaAcknowledgement: this.tpaAcknowledgement
});
this.navigateForward(this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED); this.navigateForward(this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED);
}, },
forwardButtonAction() { forwardButtonAction() {
@ -183,24 +198,22 @@ export default {
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED; scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED;
} }
break; break;
}; default:
}
this.navigateForward(scenario); this.navigateForward(scenario);
this.mainStore.saveProviderPreferenceData({selectedProvider: this.selectedProvider, tpaAcknowledgement: this.tpaAcknowledgement}); this.mainStore.saveProviderPreferenceData({
selectedProvider: this.selectedProvider,
tpaAcknowledgement: this.tpaAcknowledgement
});
} }
}, },
openStateSteeringModal() { openStateSteeringModal() {
this.$refs.StateSteeringModal.openModal(); this.$refs.StateSteeringModal.openModal();
} }
},
mounted() {
setupModalLinks(this);
const pageData = this.mainStore.pageData(issPageValues.PROVIDER_PREFERENCE);
this.selectedProvider = pageData?.selectedProvider ? pageData.selectedProvider : null;
this.tpaAcknowledgement = pageData?.tpaAcknowledgement ? pageData.tpaAcknowledgement : false;
} }
}; };
</script> </script>
<style lang="scss" > <style lang="scss" scoped>
#sub-header span{ #sub-header span{
color: $black; color: $black;
} }

View file

@ -1,9 +1,8 @@
import shopPreferenceModal from './shop-preference-modal.vue'
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js'; import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import shopPreferenceModal from './shop-preference-modal';
// Mock our module for promises. // Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => ({ jest.mock('@/helpers/layout-helper.js', () => ({
@ -16,29 +15,6 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn() setupModalLinks: jest.fn()
})); }));
describe('provider-preference.vue', () => {
test('Should display state specific steering modal link when in state with steering language', async () => {
// Arrange
const { wrapper } = setupMocks({showSteeringLink: true});
// Act
// Test
expect(wrapper.vm.showSteeringLink).toBeTruthy();
});
test('Should not display state specific steering modal link when not in state with steering language', async () => {
// Arrange
const { wrapper } = setupMocks({showSteeringLink: false});
// Act
// Test
expect(wrapper.vm.showSteeringLink).toBeFalsy();
});
});
function setupMocks(propsData) { function setupMocks(propsData) {
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
@ -48,15 +24,35 @@ function setupMocks(propsData) {
mountOptions.propsData = propsData; mountOptions.propsData = propsData;
const wrapper = shallowMount( const wrapper = shallowMount(shopPreferenceModal,
shopPreferenceModal, mountOptions);
mountOptions
);
const apiResponses = {}; const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses); settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation (() => {}); fetchCmsContentForPage.mockImplementation(() => { });
return { wrapper }; return { wrapper };
} }
describe('provider-preference.vue', () => {
test('Should display state specific steering modal link when in state with steering language', async () => {
// Arrange
const { wrapper } = setupMocks({ showSteeringLink: true });
// Act
// Test
expect(wrapper.vm.showSteeringLink).toBeTruthy();
});
test('Should not display state specific steering modal link when not in state with steering language', async () => {
// Arrange
const { wrapper } = setupMocks({ showSteeringLink: false });
// Act
// Test
expect(wrapper.vm.showSteeringLink).toBeFalsy();
});
});

View file

@ -4,14 +4,18 @@
:modalId="ModalName" :modalId="ModalName"
:footerButtonText="ModalCloseButtonText" :footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick"> @footer-button-event="footerButtonClick">
<h5 class="text-center mb-4">{{ModalHeadline}}</h5> <h5 class="text-center mb-4">
<div> {{ ModalHeadline }}
<div class="mb-4" v-html="ModalBodyText"></div> </h5>
<a <div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<a
v-if="showSteeringLink"
class="modal-text" class="modal-text"
v-if="showSteeringLink" @click="openSteeringModal"
v-html="ModalSubBodyText" v-html="ModalSubBodyText">
@click="openSteeringModal">
</a> </a>
</div> </div>
</modal> </modal>
@ -23,6 +27,9 @@ import { states } from '@/constants/states';
export default { export default {
name: 'content-group-modal', name: 'content-group-modal',
components: {
modal
},
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
showSteeringLink: Boolean showSteeringLink: Boolean
@ -42,8 +49,8 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'BodyText'); return this.getCmsContent(this.cmsWidgetName, 'BodyText');
}, },
ModalSubBodyText() { ModalSubBodyText() {
let header = this.getCmsContent(this.cmsWidgetName, 'BodyText2'); const header = this.getCmsContent(this.cmsWidgetName, 'BodyText2');
return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]) return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]);
}, },
ModalImage() { ModalImage() {
return this.getCmsContent(this.cmsWidgetName, 'Image'); return this.getCmsContent(this.cmsWidgetName, 'Image');
@ -59,18 +66,15 @@ export default {
footerButtonClick() { footerButtonClick() {
this.$refs[this.ModalName].closeModal(); this.$refs[this.ModalName].closeModal();
}, },
openSteeringModal(){ openSteeringModal() {
this.$refs[this.ModalName].closeModal(); this.$refs[this.ModalName].closeModal();
this.$emit('openSteering'); this.$emit('openSteering');
} }
},
components: {
modal
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal { .modal {
&.modal-component { &.modal-component {
.modal-dialog { .modal-dialog {

View file

@ -1,10 +1,9 @@
import steeringModal from './steering-modal.vue'
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js'; import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import steeringModal from './steering-modal';
// Mock our module for promises. // Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => ({ jest.mock('@/helpers/layout-helper.js', () => ({
@ -17,31 +16,29 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn() setupModalLinks: jest.fn()
})); }));
describe('steering-modal.vue', () => { function setupMocks() {
test('getStateSpecificText should return true if state matches', () => {
const { wrapper } = setupMocks();
useMainStore().order.customer.address.state = 'OH';
expect(wrapper.vm.getStateSpecificText('OH')).toBeTruthy();
})
});
function setupMocks(){
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
navigate: jest.fn() navigate: jest.fn()
} }
}); });
const wrapper = shallowMount( const wrapper = shallowMount(steeringModal,
steeringModal, mountOptions);
mountOptions
);
const apiResponses = {}; const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses); settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation (() => {}); fetchCmsContentForPage.mockImplementation(() => { });
return { wrapper }; return { wrapper };
} }
describe('steering-modal.vue', () => {
test('getStateSpecificText should return true if state matches', () => {
const { wrapper } = setupMocks();
useMainStore().order.customer.address.state = 'OH';
expect(wrapper.vm.getStateSpecificText('OH')).toBeTruthy();
});
});

View file

@ -4,10 +4,16 @@
:modalId="ModalName" :modalId="ModalName"
:footerButtonText="ModalCloseButtonText" :footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick"> @footer-button-event="footerButtonClick">
<h5 class="text-center mb-4">{{ModalHeadline}}</h5> <h5 class="text-center mb-4">
{{ ModalHeadline }}
</h5>
<div> <div>
<div class="mb-4" v-html="ModalBodyText"></div> <div
<div v-if="ModalSubBodyText" v-html="ModalSubBodyText"></div> class="mb-4"
v-html="ModalBodyText"></div>
<div
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></div>
</div> </div>
</modal> </modal>
</template> </template>
@ -19,6 +25,9 @@ import { states } from '@/constants/states';
export default { export default {
name: 'content-group-modal', name: 'content-group-modal',
components: {
modal
},
props: { props: {
cmsWidgetName: String cmsWidgetName: String
}, },
@ -27,8 +36,8 @@ export default {
return this.cmsWidgetName; return this.cmsWidgetName;
}, },
ModalHeadline() { ModalHeadline() {
let header = this.getCmsContent(this.cmsWidgetName, 'HeaderText'); const header = this.getCmsContent(this.cmsWidgetName, 'HeaderText');
return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]) return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]);
}, },
ModalSubheadertext() { ModalSubheadertext() {
return this.getCmsContent(this.cmsWidgetName, 'SubheaderText'); return this.getCmsContent(this.cmsWidgetName, 'SubheaderText');
@ -60,14 +69,11 @@ export default {
getStateSpecificText(value) { getStateSpecificText(value) {
return this.mainStore.order.customer.address.state?.toLowerCase() == value?.toLowerCase(); return this.mainStore.order.customer.address.state?.toLowerCase() == value?.toLowerCase();
} }
},
components: {
modal
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal { .modal {
&.modal-component { &.modal-component {
.modal-dialog { .modal-dialog {

View file

@ -1,9 +1,8 @@
import tpaRecalModal from './tpa-recal-modal.vue'
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js'; import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import tpaRecalModal from './tpa-recal-modal';
// Mock our module for promises. // Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => ({ jest.mock('@/helpers/layout-helper.js', () => ({
@ -16,6 +15,24 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn() setupModalLinks: jest.fn()
})); }));
function setupMocks() {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const wrapper = shallowMount(tpaRecalModal,
mountOptions);
const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => { });
return { wrapper };
}
describe('tpa-Recal-Modal.vue', () => { describe('tpa-Recal-Modal.vue', () => {
test('should show error if not acknowledged', () => { test('should show error if not acknowledged', () => {
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();
@ -23,7 +40,7 @@ describe('tpa-Recal-Modal.vue', () => {
wrapper.vm.footerButtonClick(); wrapper.vm.footerButtonClick();
expect(wrapper.vm.showError).toBeTruthy(); expect(wrapper.vm.showError).toBeTruthy();
}) });
test('should not show error if acknowledged', () => { test('should not show error if acknowledged', () => {
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();
@ -31,25 +48,5 @@ describe('tpa-Recal-Modal.vue', () => {
wrapper.vm.footerButtonClick(); wrapper.vm.footerButtonClick();
expect(wrapper.vm.showError).toBeFalsy(); expect(wrapper.vm.showError).toBeFalsy();
})
});
function setupMocks(){
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
}); });
});
const wrapper = shallowMount(
tpaRecalModal,
mountOptions
);
const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation (() => {});
return { wrapper };
}

View file

@ -3,14 +3,22 @@
:ref="ModalName" :ref="ModalName"
:modalId="ModalName" :modalId="ModalName"
:footerButtonText="ModalCloseButtonText" :footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick" :isButtonDisabled="isButtonDisabled"
:isButtonDisabled="isButtonDisabled"> @footer-button-event="footerButtonClick">
<div class="form-test-invalid"> <div class="form-test-invalid">
<h5 class="text-center mb-4">{{ModalHeadline}}</h5> <h5 class="text-center mb-4">
{{ ModalHeadline }}
</h5>
<div> <div>
<div class="mb-4" v-html="ModalBodyText"></div> <div
<tpaRecalToggle class="mb-5" cmsWidgetName="TPARecalModalToggle"/> class="mb-4"
v-html="ModalBodyText"></div>
<tpaRecalToggle
class="mb-5"
cmsWidgetName="TPARecalModalToggle" />
<checkBox <checkBox
ref="tpaAcknowledgement"
v-model="acknowledged"
class="mb-2" class="mb-2"
:class="showError && ' has-error'" :class="showError && ' has-error'"
:validationRules="rules.optionRequired" :validationRules="rules.optionRequired"
@ -19,12 +27,11 @@
:tabIndex="0" :tabIndex="0"
:checkboxLabel="ModalSubBodyText" :checkboxLabel="ModalSubBodyText"
:screenReaderOnlyText="ModalSubBodyText" :screenReaderOnlyText="ModalSubBodyText"
isRequired isRequired />
ref="tpaAcknowledgement" <div
v-model="acknowledged" v-if="showError"
/> class="row form-test-error mt-1">
<div v-if="showError" class="row form-test-error mt-1"> <p>{{ ackError }}</p>
<p>{{ackError}}</p>
</div> </div>
</div> </div>
</div> </div>
@ -33,16 +40,22 @@
<script> <script>
import modal from '@/digital-components/modal/modal'; import modal from '@/digital-components/modal/modal';
import tpaRecalToggle from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-toggle/tpa-recal-toggle.vue' import tpaRecalToggle from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-toggle/tpa-recal-toggle';
import checkBox from '@/ux-components/checkbox/checkbox.vue' import checkBox from '@/ux-components/checkbox/checkbox';
import globalRules from '@/constants/global-rules'; import globalRules from '@/constants/global-rules';
export default { export default {
name: 'content-group-modal', name: 'content-group-modal',
components: {
modal,
tpaRecalToggle,
checkBox
},
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
ackError: String ackError: String
}, },
emits: ['buttonClick'],
data() { data() {
return { return {
acknowledged: false, acknowledged: false,
@ -52,7 +65,6 @@ export default {
} }
}; };
}, },
emits: ['buttonClick'],
computed: { computed: {
ModalName() { ModalName() {
return this.cmsWidgetName; return this.cmsWidgetName;
@ -70,7 +82,12 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'FooterText'); return this.getCmsContent(this.cmsWidgetName, 'FooterText');
}, },
isButtonDisabled() { isButtonDisabled() {
return !this.acknowledged return !this.acknowledged;
}
},
watch: {
acknowledged() {
this.showError = false;
} }
}, },
methods: { methods: {
@ -80,8 +97,7 @@ export default {
footerButtonClick() { footerButtonClick() {
// check if acked, if not show error // check if acked, if not show error
if (this.acknowledged) if (this.acknowledged) {
{
this.$refs[this.ModalName]?.closeModal(); this.$refs[this.ModalName]?.closeModal();
this.$emit('buttonClick'); this.$emit('buttonClick');
} else { } else {
@ -89,21 +105,11 @@ export default {
this.showError = true; this.showError = true;
} }
} }
},
watch: {
acknowledged() {
this.showError = false;
}
},
components: {
modal,
tpaRecalToggle,
checkBox
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal { .modal {
&.modal-component { &.modal-component {
.modal-dialog { .modal-dialog {

View file

@ -1,11 +1,10 @@
/* eslint-env jest */ /* eslint-env jest */
import { render } from '@testing-library/vue'; import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { issPageValues } from '@/router/router-constants/issPage-values'; import { issPageValues } from '@/router/router-constants/issPage-values';
import { queryStrings } from '@/constants/query-strings'; import { queryStrings } from '@/constants/query-strings';
import { GaActions } from '@/constants/analytics'; import { GaActions } from '@/constants/analytics';
import tpaRecalToggle from './tpa-recal-toggle.vue'; import tpaRecalToggle from './tpa-recal-toggle';
const mockText = Object.freeze({ const mockText = Object.freeze({
HEADER: 'Mock Header', HEADER: 'Mock Header',
@ -72,7 +71,7 @@ describe('more details section', () => {
test('should toggle when arrow clicked', async () => { test('should toggle when arrow clicked', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
const { container } = render(tpaRecalToggle, mountOptions); const { container } = render(tpaRecalToggle, mountOptions);
const toggleLink = container.querySelector('#moreDetails');; const toggleLink = container.querySelector('#moreDetails');
await user.click(toggleLink); await user.click(toggleLink);
let moreDetailsSection = container.querySelector('#tpa-recal-detail-wrapper'); let moreDetailsSection = container.querySelector('#tpa-recal-detail-wrapper');

View file

@ -3,41 +3,37 @@
<div <div
class="tpa-toggle" class="tpa-toggle"
:class="[isDetailVisible ? 'active' : '']" :class="[isDetailVisible ? 'active' : '']"
@click="handleClickToggle" @click="handleClickToggle">
>
<textLink <textLink
id="moreDetails"
linkType="text" linkType="text"
href="#!" href="#!"
:text="textForToggle" :text="textForToggle" />
id="moreDetails"
/>
</div> </div>
<Transition> <Transition>
<div <div
v-show="isDetailVisible" v-show="isDetailVisible"
id="tpa-recal-detail-wrapper" id="tpa-recal-detail-wrapper"
class="mt-2" class="mt-2">
>
<div <div
v-html="detailContent"
id="tpa-recal-detail-content" id="tpa-recal-detail-content"
class="mb-2" class="mb-2"
/> v-html="detailContent" />
</div> </div>
</Transition> </Transition>
</div> </div>
</template> </template>
<script> <script>
import textLink from '@/ux-components/text-link/text-link.vue'; import textLink from '@/ux-components/text-link/text-link';
export default { export default {
name: 'tpa-recal-toggle', name: 'tpa-recal-toggle',
props: {
cmsWidgetName: String
},
components: { components: {
textLink textLink
}, },
props: {
cmsWidgetName: String
},
data() { data() {
return { return {
isDetailVisible: false isDetailVisible: false
@ -58,7 +54,7 @@ export default {
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.tpa-recal-toggle { .tpa-recal-toggle {
.tpa-toggle { .tpa-toggle {
&::after { &::after {

View file

@ -1,17 +1,20 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" > <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget"/> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 review"> <div class="container-fluid pb-2 review">
<p>Placeholder for review page</p> <p>Placeholder for review page</p>
<siteFooter <siteFooter
cmsWidgetName="SiteFooterWidget" ref="siteFooter"
ref="siteFooter" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ForwardClicked="navigateForward" @ForwardClicked="navigateForward"
@back-clicked="backButtonAction" @back-clicked="backButtonAction" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -27,15 +30,16 @@ import { settleAllPromises } from '@/helpers/layout-helper';
import { Form } from 'vee-validate'; import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js'; import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
export default { export default {
name: 'review-page', name: 'review-page',
components: {
siteHeader,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
data() {
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -46,33 +50,31 @@ export default {
promise: cmsContentPromise promise: cmsContentPromise
}]; }];
// use resultMap to populate layout content. // use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
}, },
methods: setup() {
{ const mainStore = useMainStore();
backButtonAction() { return { mainStore };
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route); },
}, data() {
forwardButtonAction() { },
}, methods: {
navigateForward() { backButtonAction() {
this.$router.navigate( this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
}
}, },
components: { forwardButtonAction() {
siteHeader, },
siteFooter, navigateForward() {
Form this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
this.$route);
}
} }
} };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.review p{ .review p{
margin-bottom:0.75rem; margin-bottom:0.75rem;
} }

View file

@ -1,18 +1,21 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" > <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget"/> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2"> <div class="container-fluid pb-2">
<p>Placeholder for schedule page</p> <p>Placeholder for schedule page</p>
<siteFooter <siteFooter
class="mt-5" ref="siteFooter"
cmsWidgetName="SiteFooterWidget" class="mt-5"
ref="siteFooter" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" @back-clicked="backButtonAction" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -32,16 +35,14 @@ import { useMainStore } from '@/store';
export default { export default {
name: 'schedule-page', name: 'schedule-page',
components: {
siteHeader,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
data() { async beforeRouteEnter(to, from, next) {
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
async beforeRouteEnter(to, from, next)
{
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -53,11 +54,18 @@ export default {
}]; }];
// use resultMap to populate layout content. // use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
}); });
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
}, },
methods: methods:
{ {
@ -72,13 +80,6 @@ export default {
navigateForward() { navigateForward() {
} }
}, }
components: { };
siteHeader,
siteFooter,
Form
}
}
</script> </script>
<style lang="scss">
</style>

View file

@ -1,16 +1,24 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import serviceLocation from '@/layouts/service-location/service-location.vue'; import serviceLocation from '@/layouts/service-location/service-location';
import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper';
// Define Mocks // Define Mocks
jest.mock('@/helpers/cms-content-helper', () => ({ jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(() => { fetchCmsContentForPage: jest.fn(() => Promise.resolve('content'))
return Promise.resolve('content');
})
})); }));
const mockGetServiceabilityDetails = (mockServiceZipCode) => { function setupMocks() {
const wrapper = shallowMount(serviceLocation,
getMountOptions({
router: {
navigate: jest.fn()
}
}));
return { wrapper };
}
const mockGetServiceabilityDetails = () => {
const serviceabilityDetails = { const serviceabilityDetails = {
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
@ -21,18 +29,15 @@ const mockGetServiceabilityDetails = (mockServiceZipCode) => {
return Promise.resolve(serviceabilityDetails); return Promise.resolve(serviceabilityDetails);
}; };
jest.mock( jest.mock('@/helpers/service-location-helper',
'@/helpers/service-location-helper',
() => ({ () => ({
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode))
return mockGetServiceabilityDetails(mockServiceZipCode); }));
})
})
);
const mockMixin = { const mockMixin = {
methods: { methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => { getCmsContent: jest.fn((widgetName, cmsFieldName) => {
// linkWidgetName is not defined. This whole spec file needs review.
if (widgetName === linkWidgetName) { if (widgetName === linkWidgetName) {
return mockLinkCmsContent[cmsFieldName]; return mockLinkCmsContent[cmsFieldName];
} }
@ -130,7 +135,7 @@ describe('updating service zip', () => {
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
zipCode: '45433', zipCode: '45433',
state: 'OH', state: 'OH'
}; };
// Act // Act
@ -140,16 +145,3 @@ describe('updating service zip', () => {
expect(wrapper.vm.zipContainsMilitaryBase).toBe(true); expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
}); });
}); });
function setupMocks() {
const wrapper = shallowMount(
serviceLocation,
getMountOptions({
router: {
navigate: jest.fn()
}
})
);
return { wrapper };
}

View file

@ -1,44 +1,50 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" > <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative"> <div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" /> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" id="sub-header" class="mt-5" /> <siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeaderWidget"
class="mt-5" />
<div class="select-car"> <div class="select-car">
<div class="container-fluid pb-2"> <div class="container-fluid pb-2">
<div class="row px-3"> <div class="row px-3">
<div class="col"> <div class="col">
<div class="select-car-form rounded"> <div class="select-car-form rounded">
<serviceZipModalQuestion <serviceZipModalQuestion
v-model="serviceZipCodeQuestion"
ref="serviceZipCodeQuestion" ref="serviceZipCodeQuestion"
v-model="serviceZipCodeQuestion"
modalWidgetName="ServiceZipModalWidget"
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase" />
modalWidgetName="ServiceZipModalWidget" />
<alert <alert
v-if="displayMilitaryZipAlert"
ref="alertMilitaryBaseZip" ref="alertMilitaryBaseZip"
class="my-5" class="my-5"
cmsWidgetName="AlertMilitaryBaseZipWidget" cmsWidgetName="AlertMilitaryBaseZipWidget"
v-if="displayMilitaryZipAlert"
alertClass="alert-warning" /> alertClass="alert-warning" />
<buttonQuestion <buttonQuestion
v-model="selectedAppointmentType"
cmsWidgetName="ServiceTypeQuestionWidget" cmsWidgetName="ServiceTypeQuestionWidget"
:questionText="questionText" :questionText="questionText"
:answers="answersFromCms" :answers="answersFromCms"
buttonTypeString="listCard" buttonTypeString="listCard"
isRequired isRequired
v-model="selectedAppointmentType"
validationRules="selection-required" validationRules="selection-required"
class="service-location-button-question mb-1"/> class="service-location-button-question mb-1" />
<div class="select-car"> <div class="select-car">
<siteFooter <siteFooter
ref="siteFooter"
class="mt-5" class="mt-5"
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@backClicked="backButtonAction" @backClicked="backButtonAction"
@forwardClicked="forwardButtonAction" @forwardClicked="forwardButtonAction" />
ref="siteFooter"
/>
</div> </div>
</div> </div>
</div> </div>
@ -62,17 +68,26 @@ import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-loca
// Import Component // Import Component
import alert from '@/ux-components/alert/alert'; import alert from '@/ux-components/alert/alert';
import baseFormMixin from '@/mixins/base-form-mixin'; import baseFormMixin from '@/mixins/base-form-mixin';
import baseMixin from '@/mixins/base-mixin';
import { Form, defineRule } from 'vee-validate'; import { Form, defineRule } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import siteFooter from '@/iss-components/site-footer/site-footer';
import siteHeader from '@/iss-components/site-header/site-header.vue'; import siteHeader from '@/iss-components/site-header/site-header';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header';
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue'; import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question';
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule('selection-required', required(errorMessages.OPTION_REQUIRED)); defineRule('selection-required', required(errorMessages.OPTION_REQUIRED));
export default { export default {
name: 'service-location', name: 'service-location',
components: {
siteFooter,
siteHeader,
siteSubHeader,
buttonQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
serviceZipModalQuestion,
alert
},
mixins: [baseFormMixin], mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
@ -103,10 +118,8 @@ export default {
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.setData( vm.setData(resultMap.zipCodeData,
resultMap.zipCodeData, resultMap.serviceabilityDetails);
resultMap.serviceabilityDetails
)
}); });
}, },
setup() { setup() {
@ -132,25 +145,25 @@ export default {
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers'); return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
}, },
serviceZipCodeQuestion: { serviceZipCodeQuestion: {
get: function() { get() {
return { return {
zipCode: this.zipCode, zipCode: this.zipCode,
state: this.state state: this.state
}; };
}, },
set: function (newValue) { set(newValue) {
this.zipCode = newValue.zipCode; this.zipCode = newValue.zipCode;
this.state = newValue.state; this.state = newValue.state;
// TODO: review - this seems like it should be awaited.
this.$nextTick(); this.$nextTick();
} }
}, },
isServiceableMobile() { isServiceableMobile() {
if (this.isRecalibrationServiceableMobile !== null) { if (this.isRecalibrationServiceableMobile !== null) {
return this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile; return this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile;
} else {
return this.isGlassServiceableMobile;
} }
return this.isGlassServiceableMobile;
}, },
displayMilitaryZipAlert() { displayMilitaryZipAlert() {
return this.zipContainsMilitaryBase && this.isServiceableMobile; return this.zipContainsMilitaryBase && this.isServiceableMobile;
@ -188,26 +201,17 @@ export default {
}, },
setServiceabilityDetails(serviceabilityDetails) { setServiceabilityDetails(serviceabilityDetails) {
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop; this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
this.isRecalibrationServiceableInshop = this.isRecalibrationServiceableInshop
serviceabilityDetails.isRecalibrationServiceableInshop; = serviceabilityDetails.isRecalibrationServiceableInshop;
this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile; this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile = this.isRecalibrationServiceableMobile
serviceabilityDetails.isRecalibrationServiceableMobile; = serviceabilityDetails.isRecalibrationServiceableMobile;
} }
},
components: {
siteFooter,
siteHeader,
siteSubHeader,
buttonQuestion,
Form,
serviceZipModalQuestion,
alert
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.page-container-grouped-styles { .page-container-grouped-styles {
overflow: auto; overflow: auto;

View file

@ -1,13 +1,11 @@
import { mount, shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import serviceZipModalQuestion from './service-zip-modal-question';
import crypto from 'crypto'; import crypto from 'crypto';
import serviceZipModalQuestion from './service-zip-modal-question';
global.crypto = crypto; global.crypto = crypto;
jest.mock('@/digital-components/textbox-question/textbox-question', () => ({ jest.mock('@/digital-components/textbox-question/textbox-question', () => ({
getCmsContent: jest.fn((widgetName, cmsFieldName) => { getCmsContent: jest.fn((widgetName, cmsFieldName) => widgetName[cmsFieldName])
return widgetName[cmsFieldName];
})
})); }));
jest.mock('@/digital-components/modal/modal', () => ({ jest.mock('@/digital-components/modal/modal', () => ({
@ -17,7 +15,7 @@ jest.mock('@/digital-components/modal/modal', () => ({
} }
})); }));
const mockGetServiceabilityDetails = (mockServiceZipCode) => { const mockGetServiceabilityDetails = () => {
const serviceabilityDetails = { const serviceabilityDetails = {
isGlassServiceableInshop: true, isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true, isRecalibrationServiceableInshop: true,
@ -53,19 +51,13 @@ const mockGetZipCodeData = (mockServiceZipCode) => {
state: null, state: null,
zipCodeCtu: null zipCodeCtu: null
}; };
} };
jest.mock( jest.mock('@/helpers/service-location-helper',
'@/helpers/service-location-helper',
() => ({ () => ({
getServiceabilityDetails: jest.fn((mockServiceZipCode) => { getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode)),
return mockGetServiceabilityDetails(mockServiceZipCode); getZipCodeData: jest.fn((mockServiceZipCode) => mockGetZipCodeData(mockServiceZipCode))
}), }));
getZipCodeData: jest.fn((mockServiceZipCode) => {
return mockGetZipCodeData(mockServiceZipCode);
})
})
);
const linkWidgetName = 'linkWidgetName'; const linkWidgetName = 'linkWidgetName';
const modalWidgetName = 'modalWidgetName'; const modalWidgetName = 'modalWidgetName';
@ -91,12 +83,12 @@ const mockMixin = {
return null; return null;
}) })
} }
} };
describe('service-zip-modal-question.vue', () => { describe('service-zip-modal-question.vue', () => {
it('Initial state on load with existing location info', async () => { it('Initial state on load with existing location info', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'OH', state: 'OH',
zipCode: '43235' zipCode: '43235'
}; };
@ -105,19 +97,19 @@ describe('service-zip-modal-question.vue', () => {
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
linkWidgetName: linkWidgetName, linkWidgetName,
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });
// Assert // Assert
expect(wrapper.html()).not.toEqual(expect.stringContaining(mockLinkCmsContent['BodyText'])); expect(wrapper.html()).not.toEqual(expect.stringContaining(mockLinkCmsContent.BodyText));
}); });
it('Should emit update:modelValue on setZipCode for a valid, serviceable zip', async () => { it('Should emit update:modelValue on setZipCode for a valid, serviceable zip', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'IL', state: 'IL',
zipCode: '61606' zipCode: '61606'
}; };
@ -129,7 +121,7 @@ describe('service-zip-modal-question.vue', () => {
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
mobileFeePart: {}, mobileFeePart: {},
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });
@ -139,7 +131,7 @@ describe('service-zip-modal-question.vue', () => {
await wrapper.vm.setZipCode(); await wrapper.vm.setZipCode();
let expectedEmit = [[{ state: 'OH', zipCode: '43235' }]]; const expectedEmit = [[{ state: 'OH', zipCode: '43235' }]];
// Assert // Assert
expect(wrapper.emitted('update:modelValue')).toEqual(expectedEmit); expect(wrapper.emitted('update:modelValue')).toEqual(expectedEmit);
@ -147,7 +139,7 @@ describe('service-zip-modal-question.vue', () => {
it('Should not emit update:modelValue on setZipCode for an invalid zip', async () => { it('Should not emit update:modelValue on setZipCode for an invalid zip', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'IL', state: 'IL',
zipCode: '61606' zipCode: '61606'
}; };
@ -158,7 +150,7 @@ describe('service-zip-modal-question.vue', () => {
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });
@ -173,7 +165,7 @@ describe('service-zip-modal-question.vue', () => {
it('Should display invalid zip alerts for invalid ip inputs', async () => { it('Should display invalid zip alerts for invalid ip inputs', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'IL', state: 'IL',
zipCode: '61606' zipCode: '61606'
}; };
@ -184,7 +176,7 @@ describe('service-zip-modal-question.vue', () => {
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });
@ -199,7 +191,7 @@ describe('service-zip-modal-question.vue', () => {
it('Should reset all alerts on onModalClosed', async () => { it('Should reset all alerts on onModalClosed', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'IL', state: 'IL',
zipCode: '61606' zipCode: '61606'
}; };
@ -210,7 +202,7 @@ describe('service-zip-modal-question.vue', () => {
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });
@ -226,7 +218,7 @@ describe('service-zip-modal-question.vue', () => {
it('Should prepopulate the zip on onModalOpened', async () => { it('Should prepopulate the zip on onModalOpened', async () => {
// Arrange // Arrange
let serviceZipCodeQuestion = { const serviceZipCodeQuestion = {
state: 'IL', state: 'IL',
zipCode: '61606' zipCode: '61606'
}; };
@ -237,7 +229,7 @@ describe('service-zip-modal-question.vue', () => {
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: serviceZipCodeQuestion, modelValue: serviceZipCodeQuestion,
modalWidgetName: modalWidgetName modalWidgetName
}, },
attachTo: document.body attachTo: document.body
}); });

View file

@ -6,8 +6,8 @@
linkType="text" linkType="text"
:text="serviceZipLinkText" :text="serviceZipLinkText"
href="#!" href="#!"
@click-event="openModal" aria-label="Modal window"
aria-label="Modal window" /> @click-event="openModal" />
</div> </div>
</div> </div>
<modal <modal
@ -16,40 +16,38 @@
:onModalOpenedCallback="onModalOpened" :onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed" :onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText" :footerButtonText="modalFooterText"
@footer-button-event="setZipCode" > @footer-button-event="setZipCode">
<serviceZipQuestion <serviceZipQuestion
ref="serviceZipCodeQuestion" ref="serviceZipCodeQuestion"
customInputId="serviceZipCode"
v-model="internalModel.zipCode" v-model="internalModel.zipCode"
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }" customInputId="serviceZipCode"
:cmsWidgetName="textboxQuestionWidgetName" /> :cmsWidgetName="textboxQuestionWidgetName"
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }" />
<alert <alert
ref="alertInvalidZip"
v-if="displayInvalidZipAlert" v-if="displayInvalidZipAlert"
ref="alertInvalidZip"
class="my-4" class="my-4"
:cmsWidgetName="alertInvalidZipWidgetName" :cmsWidgetName="alertInvalidZipWidgetName"
alertClass="alert-danger" alertClass="alert-danger"
v-bind:isDismissible="false" /> :isDismissible="false" />
</modal> </modal>
</template> </template>
<script> <script>
import textLink from '@/ux-components/text-link/text-link'; import textLink from '@/ux-components/text-link/text-link';
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question'; import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question';
import modal from '@/digital-components/modal/modal.vue' import modal from '@/digital-components/modal/modal';
import alert from '@/ux-components/alert/alert'; import alert from '@/ux-components/alert/alert';
import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper'; import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper';
export default { export default {
name: 'service-zip-modal-question', name: 'service-zip-modal-question',
emits: ['update:modelValue', 'updated-serviceability', 'updated-contains-military-base'], components: {
data() { textLink,
return { modal,
internalModel: this.copyModel(this.modelValue), serviceZipQuestion,
serviceZipCodeTextInputId: '', alert
displayInvalidZipAlert: false
};
}, },
props: { props: {
modelValue: { modelValue: {
@ -61,6 +59,7 @@ export default {
}, },
modalWidgetName: String modalWidgetName: String
}, },
emits: ['update:modelValue', 'updated-serviceability', 'updated-contains-military-base'],
setup() { setup() {
const textboxQuestionWidgetName = 'ServiceZipQuestionWidget'; const textboxQuestionWidgetName = 'ServiceZipQuestionWidget';
const alertInvalidZipWidgetName = 'AlertInvalidZipWidget'; const alertInvalidZipWidgetName = 'AlertInvalidZipWidget';
@ -69,11 +68,19 @@ export default {
alertInvalidZipWidgetName alertInvalidZipWidgetName
}; };
}, },
data() {
return {
internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: '',
displayInvalidZipAlert: false
};
},
computed: { computed: {
serviceZipLinkText() { serviceZipLinkText() {
if (this.modelValue.zipCode && this.modelValue.zipCode.length > 0) { if (this.modelValue.zipCode && this.modelValue.zipCode.length > 0) {
return this.modelValue.state + ', ' + this.modelValue.zipCode; return `${this.modelValue.state}, ${this.modelValue.zipCode}`;
} }
return null;
}, },
modalHeaderText() { modalHeaderText() {
return this.getCmsContent(this.textboxQuestionWidgetName, 'QuestionText'); return this.getCmsContent(this.textboxQuestionWidgetName, 'QuestionText');
@ -85,6 +92,16 @@ export default {
return this.modalWidgetName; return this.modalWidgetName;
} }
}, },
watch: {
modelValue(newValue) {
this.internalModel = this.copyModel(newValue);
},
'internalModel.zipCode': {
handler() {
this.resetsOnZipInput();
}
}
},
methods: { methods: {
resetAlerts() { resetAlerts() {
this.displayInvalidZipAlert = false; this.displayInvalidZipAlert = false;
@ -152,22 +169,6 @@ export default {
} }
}, },
getZipCodeData getZipCodeData
},
watch: {
modelValue(newValue) {
this.internalModel = this.copyModel(newValue);
},
'internalModel.zipCode': {
handler() {
this.resetsOnZipInput();
}
}
},
components: {
textLink,
modal,
serviceZipQuestion,
alert
} }
}; };
</script> </script>

View file

@ -31,7 +31,6 @@ describe('service-zip-question.vue', () => {
}); });
// Act // Act
const modelValueText = wrapper.vm.value;
wrapper.vm.value = 'test also'; wrapper.vm.value = 'test also';
// Assert // Assert

View file

@ -1,8 +1,8 @@
<template> <template>
<textboxQuestion <textboxQuestion
ref="zipInputTextQuestion" ref="zipInputTextQuestion"
:cmsWidgetName="cmsWidgetName"
v-model="value" v-model="value"
:cmsWidgetName="cmsWidgetName"
inputId="serviceZipCode" inputId="serviceZipCode"
questionAlignment="center" questionAlignment="center"
cornerStyle="rounded" cornerStyle="rounded"
@ -26,6 +26,9 @@ defineRule('zip-format', regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVIC
export default { export default {
name: 'service-zip-question', name: 'service-zip-question',
components: {
textboxQuestion
},
props: { props: {
modelValue: { modelValue: {
serviceZipCode: String serviceZipCode: String
@ -34,16 +37,13 @@ export default {
}, },
computed: { computed: {
value: { value: {
get: function () { get() {
return this.modelValue; return this.modelValue;
}, },
set: function (newValue) { set(newValue) {
this.$emit('update:modelValue', newValue); this.$emit('update:modelValue', newValue);
} }
} }
},
components: {
textboxQuestion
} }
}; };
</script> </script>

View file

@ -1,41 +1,38 @@
export const allGlassPartsAndItemsHavePrices = (lineItems) => { const hasPrice = (lineItem) => {
if (lineItems.glassParts && Array.isArray(lineItems.glassParts)) { const value = (!!lineItem?.kitPrice && lineItem.kitPrice !== 0)
if (!processLineItems(lineItems.glassParts)) || (!!lineItem?.laborAmount && lineItem.laborAmount !== 0)
{ || (!!lineItem?.sellingPrice && lineItem.sellingPrice !== 0);
return false; return value;
}
};
if (lineItems.supportingItems && Array.isArray(lineItems.supportingItems)) {
if (!processLineItems(lineItems.supportingItems))
{
return false;
}
};
if (lineItems.vaps && Array.isArray(lineItems.vaps)) {
if (!processLineItems(lineItems.vaps))
{
return false;
}
};
return true;
}; };
const processLineItems = (lineItems) => { const processLineItems = (lineItems) => {
let returnVal = true; let returnVal = true;
lineItems.forEach( lineItem => { lineItems.forEach((lineItem) => {
if (!hasPrice(lineItem)) { if (!hasPrice(lineItem)) {
returnVal = false; returnVal = false;
}; }
}); });
return returnVal; return returnVal;
} };
const hasPrice = (lineItem) => { export const allGlassPartsAndItemsHavePrices = (lineItems) => {
const value = (!!lineItem?.kitPrice && lineItem.kitPrice != 0) || if (lineItems.glassParts && Array.isArray(lineItems.glassParts)) {
(!!lineItem?.laborAmount && lineItem.laborAmount != 0) || if (!processLineItems(lineItems.glassParts)) {
(!!lineItem?.sellingPrice && lineItem.sellingPrice != 0); return false;
return value; }
}
if (lineItems.supportingItems && Array.isArray(lineItems.supportingItems)) {
if (!processLineItems(lineItems.supportingItems)) {
return false;
}
}
if (lineItems.vaps && Array.isArray(lineItems.vaps)) {
if (!processLineItems(lineItems.vaps)) {
return false;
}
}
return true;
}; };

View file

@ -2,7 +2,7 @@ import { allGlassPartsAndItemsHavePrices } from './service-package-helper';
it('service package helper should return false when line item has missing price', () => { it('service package helper should return false when line item has missing price', () => {
// Arrange // Arrange
const mockData = { glassParts: [{partNumber: 'DW01571GBNNOEE', sellingPrice: 25}], supportingItems: [{ laborAmount: 0 }] } const mockData = { glassParts: [{ partNumber: 'DW01571GBNNOEE', sellingPrice: 25 }], supportingItems: [{ laborAmount: 0 }] };
// Act // Act
const actual = allGlassPartsAndItemsHavePrices(mockData); const actual = allGlassPartsAndItemsHavePrices(mockData);
// Assert // Assert
@ -11,7 +11,7 @@ it('service package helper should return false when line item has missing price'
it('service package helper should return true when line item has all prices', () => { it('service package helper should return true when line item has all prices', () => {
// Arrange // Arrange
const mockData = { glassParts: [{partNumber: 'DW01571GBNNOEE', sellingPrice: 25}], laborAmount: [{ sellingPrice: 10 }] } const mockData = { glassParts: [{ partNumber: 'DW01571GBNNOEE', sellingPrice: 25 }], laborAmount: [{ sellingPrice: 10 }] };
// Act // Act
const actual = allGlassPartsAndItemsHavePrices(mockData); const actual = allGlassPartsAndItemsHavePrices(mockData);
// Assert // Assert

View file

@ -1,12 +1,13 @@
<template> <template>
<buttonQuestion ref="buttonQuestion" <buttonQuestion
:answers="servicePackageAnswers" ref="buttonQuestion"
:groupName="groupName" v-model="selectedPackageName"
buttonTypeString="servicePackageRadio" :answers="servicePackageAnswers"
:buttonTypeObject="servicePackageRadio" :groupName="groupName"
v-model="selectedPackageName" buttonTypeString="servicePackageRadio"
:validationRules="validationRules" :buttonTypeObject="servicePackageRadio"
:isRequired="isRequired" /> :validationRules="validationRules"
:isRequired="isRequired" />
</template> </template>
<script> <script>
@ -16,7 +17,7 @@ import { damageLocationsSelected as glassLocations } from '@/constants/damage-lo
import servicePackageRadio from '@/layouts/service-packages/service-package-question/service-package-radio/service-package-radio'; import servicePackageRadio from '@/layouts/service-packages/service-package-question/service-package-radio/service-package-radio';
import { partTypeStrings } from '@/constants/part-type-strings'; import { partTypeStrings } from '@/constants/part-type-strings';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper' import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper';
const packageNames = { const packageNames = {
TIER_ONE: 'TierOne', TIER_ONE: 'TierOne',
@ -27,8 +28,10 @@ const packageNames = {
const store = useMainStore(); const store = useMainStore();
export default { export default {
name: 'servicePackageQuestion', name: 'service-package-question',
emits: ['vapsItemsSelected'], components: {
buttonQuestion
},
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
groupName: String, groupName: String,
@ -36,23 +39,13 @@ export default {
isRequired: Boolean, isRequired: Boolean,
availableLineItems: [] availableLineItems: []
}, },
emits: ['vapsItemsSelected'],
data() { data() {
return { return {
servicePackageRadio: servicePackageRadio, servicePackageRadio,
selectedPackageName: packageNames.TIER_ONE selectedPackageName: packageNames.TIER_ONE
}; };
}, },
watch: {
availableLineItems() {
if (allGlassPartsAndItemsHavePrices(store.order.lineItems)) {
this.selectDefaultPackage();
}
},
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage);
}
},
computed: { computed: {
nullSafeAvailableLineItems() { nullSafeAvailableLineItems() {
if (this.availableLineItems?.lineItems) { if (this.availableLineItems?.lineItems) {
@ -64,27 +57,20 @@ export default {
servicePackageAnswers() { servicePackageAnswers() {
if (!this.cmsWidgetName) return []; if (!this.cmsWidgetName) return [];
const cmsAnswersContent = []; const cmsAnswersContent = [];
cmsAnswersContent.push( cmsAnswersContent.push({
{ Name: 'TierOne',
Name: 'TierOne', cmsWidgetName: 'EconomyServicePackage'
cmsWidgetName: 'EconomyServicePackage' });
} if (this.shouldDisplayTierTwoPackage) {
); cmsAnswersContent.push({
if (this.shouldDisplayTierTwoPackage) Name: 'TierTwo',
{ cmsWidgetName: 'StandardServicePackage'
cmsAnswersContent.push( });
{
Name: 'TierTwo',
cmsWidgetName: 'StandardServicePackage'
}
);
} }
cmsAnswersContent.push( cmsAnswersContent.push({
{ Name: 'TierThree',
Name: 'TierThree', cmsWidgetName: 'PremiumServicePackage'
cmsWidgetName: 'PremiumServicePackage' });
}
);
// if cms content has not yet loaded, skip // if cms content has not yet loaded, skip
if (this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText') == '') { if (this.getCmsContent(cmsAnswersContent[0].cmsWidgetName, 'HeaderText') == '') {
return {}; return {};
@ -104,24 +90,20 @@ export default {
return this.lineItemsContainsPartType(partTypeStrings.RECALIBRATION); return this.lineItemsContainsPartType(partTypeStrings.RECALIBRATION);
}, },
frontWipersApplicableForTierTwo() { frontWipersApplicableForTierTwo() {
const frontWipersAreAvailable = this.lineItemsContainsPartType( const frontWipersAreAvailable = this.lineItemsContainsPartType(partTypeStrings.FRONT_WIPER);
partTypeStrings.FRONT_WIPER
);
const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(glassLocations.WINDSHIELD); const glassToReplaceContainsWindshield = this.glassToReplaceContainsGlassLocation(glassLocations.WINDSHIELD);
if(frontWipersAreAvailable && (useMainStore().order.damage.isRepair || glassToReplaceContainsWindshield)) if (frontWipersAreAvailable && (useMainStore().order.damage.isRepair || glassToReplaceContainsWindshield)) {
{
return true; return true;
} }
return false; return false;
}, },
rearWiperApplicableForTierTwo() { rearWiperApplicableForTierTwo() {
const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER); const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER);
return ( return (
this.glassToReplaceContainsGlassLocation(glassLocations.REAR) && this.glassToReplaceContainsGlassLocation(glassLocations.REAR)
rearWiperIsAvailable && rearWiperIsAvailable
); );
}, },
frontWipersApplicableForTierThree() { frontWipersApplicableForTierThree() {
@ -129,29 +111,39 @@ export default {
return frontWipersAreAvailable; return frontWipersAreAvailable;
}, },
rearWiperApplicableForTierThree() { rearWiperApplicableForTierThree() {
const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER); const rearWiperIsAvailable = this.lineItemsContainsPartType(partTypeStrings.REAR_WIPER);
const frontWipersAreAvailable = this.lineItemsContainsPartType(partTypeStrings.FRONT_WIPER); const frontWipersAreAvailable = this.lineItemsContainsPartType(partTypeStrings.FRONT_WIPER);
return ( return (
rearWiperIsAvailable && rearWiperIsAvailable
(this.glassToReplaceContainsGlassLocation(glassLocations.REAR) || && (this.glassToReplaceContainsGlassLocation(glassLocations.REAR)
!frontWipersAreAvailable) || !frontWipersAreAvailable)
); );
}, },
rainDefenseApplicableForTierThree() { rainDefenseApplicableForTierThree() {
if ( if (
this.rearWiperApplicableForTierTwo && this.rearWiperApplicableForTierTwo
!this.frontWipersApplicableForTierTwo && && !this.frontWipersApplicableForTierTwo
this.frontWipersApplicableForTierThree && this.frontWipersApplicableForTierThree
) { ) {
return false; return false;
} else {
return true;
} }
return true;
}, },
shouldDisplayTierTwoPackage() { shouldDisplayTierTwoPackage() {
return this.frontWipersApplicableForTierTwo || this.rearWiperApplicableForTierTwo; return this.frontWipersApplicableForTierTwo || this.rearWiperApplicableForTierTwo;
} }
}, },
watch: {
availableLineItems() {
if (allGlassPartsAndItemsHavePrices(store.order.lineItems)) {
this.selectDefaultPackage();
}
},
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage);
}
},
methods: { methods: {
processIfStatements, processIfStatements,
getHeaderTextFromCms(cmsWidgetName) { getHeaderTextFromCms(cmsWidgetName) {
@ -170,7 +162,7 @@ export default {
}, },
getPackagePriceString(packageName) { getPackagePriceString(packageName) {
const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2); const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2);
return '+$' + formattedPriceFloat + '*'; return `+$${formattedPriceFloat}*`;
}, },
getPackagePrice(packageName) { getPackagePrice(packageName) {
let priceFloat = 0; let priceFloat = 0;
@ -184,12 +176,12 @@ export default {
getTierTwoPackageVapsPrice() { getTierTwoPackageVapsPrice() {
let vapsPrice = 0; let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForTierTwo; const priceFrontWipers = this.frontWipersApplicableForTierTwo;
const priceRearWipers = this.rearWiperApplicableForTierTwo; const priceRearWipers = this.rearWiperApplicableForTierTwo;
this.nullSafeAvailableLineItems.forEach((item) => { this.nullSafeAvailableLineItems.forEach((item) => {
if ( if (
(priceFrontWipers && (priceFrontWipers
item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) || && item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER)
(priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER) || (priceRearWipers && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER)
) { ) {
vapsPrice += this.getTotalLineItemPrice(item); vapsPrice += this.getTotalLineItemPrice(item);
} }
@ -199,16 +191,16 @@ export default {
getTierThreePackageVapsPrice() { getTierThreePackageVapsPrice() {
let vapsPrice = 0; let vapsPrice = 0;
const priceFrontWipers = this.frontWipersApplicableForTierThree; const priceFrontWipers = this.frontWipersApplicableForTierThree;
const priceRearWipers = this.rearWiperApplicableForTierThree; const priceRearWipers = this.rearWiperApplicableForTierThree;
const priceRainDefense = this.rainDefenseApplicableForTierThree; const priceRainDefense = this.rainDefenseApplicableForTierThree;
this.nullSafeAvailableLineItems.forEach((item) => { this.nullSafeAvailableLineItems.forEach((item) => {
if ( if (
(priceFrontWipers && (priceFrontWipers
item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER) || && item.partType.toUpperCase() === partTypeStrings.FRONT_WIPER)
(priceRearWipers && || (priceRearWipers
item.partType.toUpperCase() === partTypeStrings.REAR_WIPER) || && item.partType.toUpperCase() === partTypeStrings.REAR_WIPER)
(priceRainDefense && || (priceRainDefense
item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE) && item.partType.toUpperCase() === partTypeStrings.RAIN_DEFENSE)
) { ) {
vapsPrice += this.getTotalLineItemPrice(item); vapsPrice += this.getTotalLineItemPrice(item);
} }
@ -220,16 +212,15 @@ export default {
let lowestTierForPackage = packageNames.TIER_ONE; let lowestTierForPackage = packageNames.TIER_ONE;
if (vapsFromStore?.length > 0) { if (vapsFromStore?.length > 0) {
vapsFromStore.every((vapsItem) => { vapsFromStore.every((vapsItem) => {
let lowestTierForThisItem = this.getLowestTierForThisItem(vapsItem); const lowestTierForThisItem = this.getLowestTierForThisItem(vapsItem);
if (lowestTierForThisItem === packageNames.TIER_THREE) { if (lowestTierForThisItem === packageNames.TIER_THREE) {
lowestTierForPackage = packageNames.TIER_THREE; lowestTierForPackage = packageNames.TIER_THREE;
return false; return false;
} else if (lowestTierForThisItem === packageNames.TIER_TWO) { } if (lowestTierForThisItem === packageNames.TIER_TWO) {
lowestTierForPackage = packageNames.TIER_TWO; lowestTierForPackage = packageNames.TIER_TWO;
return true; return true;
} else {
return true;
} }
return true;
}); });
} }
this.selectedPackageName = lowestTierForPackage; this.selectedPackageName = lowestTierForPackage;
@ -259,6 +250,7 @@ export default {
lowestTierForThisItem = packageNames.TIER_THREE; lowestTierForThisItem = packageNames.TIER_THREE;
} }
break; break;
default:
} }
return lowestTierForThisItem; return lowestTierForThisItem;
}, },
@ -266,30 +258,20 @@ export default {
const vapsLineItemsForSelectedPackage = []; const vapsLineItemsForSelectedPackage = [];
if (packageName === packageNames.TIER_TWO) { if (packageName === packageNames.TIER_TWO) {
if (this.frontWipersApplicableForTierTwo) { if (this.frontWipersApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push( vapsLineItemsForSelectedPackage.push(...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER));
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
} }
if (this.rearWiperApplicableForTierTwo) { if (this.rearWiperApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push( vapsLineItemsForSelectedPackage.push(...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER));
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
} }
} else if (packageName === packageNames.TIER_THREE) { } else if (packageName === packageNames.TIER_THREE) {
if (this.frontWipersApplicableForTierThree) { if (this.frontWipersApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push( vapsLineItemsForSelectedPackage.push(...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER));
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
} }
if (this.rearWiperApplicableForTierThree) { if (this.rearWiperApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push( vapsLineItemsForSelectedPackage.push(...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER));
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
} }
if (this.rainDefenseApplicableForTierThree) { if (this.rainDefenseApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push( vapsLineItemsForSelectedPackage.push(...this.getLineItemsContainingPartType(partTypeStrings.RAIN_DEFENSE));
...this.getLineItemsContainingPartType(partTypeStrings.RAIN_DEFENSE)
);
} }
} }
return vapsLineItemsForSelectedPackage; return vapsLineItemsForSelectedPackage;
@ -313,9 +295,7 @@ export default {
} }
}, },
getLineItemsContainingPartType(partType) { getLineItemsContainingPartType(partType) {
const partTypeMatches = this.nullSafeAvailableLineItems.filter( const partTypeMatches = this.nullSafeAvailableLineItems.filter((lineItem) => lineItem.partType.toUpperCase() === partType);
(lineItem) => lineItem.partType.toUpperCase() === partType
);
return partTypeMatches; return partTypeMatches;
}, },
lineItemsContainsPartType(partType) { lineItemsContainsPartType(partType) {
@ -324,18 +304,13 @@ export default {
}, },
glassToReplaceContainsGlassLocation(glassLocation) { glassToReplaceContainsGlassLocation(glassLocation) {
const store = useMainStore(); const store = useMainStore();
const glassLocationMatches = const glassLocationMatches
store.order.damage.glassToReplace?.filter( = store.order.damage.glassToReplace?.filter((glassToReplace) => glassToReplace.glassLocation === glassLocation) ?? [];
(glassToReplace) => glassToReplace.glassLocation === glassLocation
) ?? [];
return !!glassLocationMatches.length; return !!glassLocationMatches.length;
}, },
getTotalLineItemPrice(lineItem) { getTotalLineItemPrice(lineItem) {
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice; return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
} }
},
components: {
buttonQuestion
} }
}; };
</script> </script>

View file

@ -1,113 +1,6 @@
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import servicePackageRadio from './service-package-radio';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin'; import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import servicePackageRadio from './service-package-radio';
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 = { const mockProps = {
buttonLabel: 'buttonLabel test copy', buttonLabel: 'buttonLabel test copy',
@ -125,3 +18,109 @@ function setupMocks({ mountOptionsMockData = {} }) {
return { wrapper }; return { wrapper };
} }
describe('service-package-radio.vue', () => {
it('Should include buttonLabel in html', async () => {
// Arrange
const { 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
const { 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
const { 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
const { 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
const { 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
const { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
const fileredResults = results.filter((result) => 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 -';
const { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
});

View file

@ -1,43 +1,58 @@
<template> <template>
<baseInputButton v-bind="$props" v-model="selectedValue"> <baseInputButton
<div class="package-label mb-4" v-bind="$props"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']" v-model="selectedValue">
for="testradio"> <div
class="package-label mb-4"
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs"> <div class="package-specs">
<p class="m-0"> <p class="m-0">
<span v-html="this.buttonLabel"></span> <span v-html="buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxiliaryCopy"></span> <span
class="pricing-info"
v-html="buttonAuxiliaryCopy"></span>
</p> </p>
<p class="sub-label m-0" <p
v-if="this.buttonLabelSubCopy" v-if="buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p> class="sub-label m-0"
v-html="buttonLabelSubCopy"></p>
<div> <div>
<ul > <ul>
<li v-for="listItem in this.arrayOfListItemsFromBodyText" :key="listItem" class="mb-0"> <li
v-for="listItem in arrayOfListItemsFromBodyText"
:key="listItem"
class="mb-0">
<!-- no textLink --> <!-- no textLink -->
<span v-if="!doesCopyContainTextLink(listItem)" <span
v-html="listItem"></span> v-if="!doesCopyContainTextLink(listItem)"
v-html="listItem"></span>
<!-- with textLink --> <!-- with textLink -->
<template v-else <template
v-for="copy in splitCopyOnCMSPlaceHolder(listItem)" v-for="copy in splitCopyOnCMSPlaceHolder(listItem)"
:key="copy"> v-else
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span> :key="copy">
<span
v-if="!doesCopyContainTextLink(copy)"
v-html="copy"></span>
<span v-else> <span v-else>
<textLink linkType="text" <textLink
:text="getRouterLinkDisplayTextFromCopy(copy)" linkType="text"
href="#!" :text="getRouterLinkDisplayTextFromCopy(copy)"
@click-event="textLinkEmit(copy)" href="#!"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)" :data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
aria-label="Modal window" /> aria-label="Modal window"
@click-event="textLinkEmit(copy)" />
</span> </span>
</template> </template>
</li> </li>
</ul> </ul>
<!-- End of body text parsing --> <!-- End of body text parsing -->
<!--ms-n6--> <!--ms-n6-->
<div class="package-footer fw-bold caption mt-4 ml-n4 mr-3" <div
v-if="this.buttonFooterCopy" v-if="buttonFooterCopy"
v-html="this.buttonFooterCopy"></div> class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-html="buttonFooterCopy"></div>
</div> </div>
</div> </div>
</div> </div>
@ -45,8 +60,8 @@
</template> </template>
<script> <script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button'; import baseInputButton from '@/digital-components/base-input-button/base-input-button';
import textLink from '@/ux-components/text-link/text-link'; import textLink from '@/ux-components/text-link/text-link';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin'; import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import { import {
getRouterLinkDisplayTextFromCopy, getRouterLinkDisplayTextFromCopy,
@ -54,14 +69,15 @@ import {
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink doesCopyContainTextLink
} from '@/helpers/cms-content-helper'; } from '@/helpers/cms-content-helper';
export default { export default {
name: 'servicePackageRadio', name: 'service-package-radio',
emits:['link-event'],
mixins: [inputButtonWrapperMixin],
components: { components: {
baseInputButton, baseInputButton,
textLink textLink
}, },
mixins: [inputButtonWrapperMixin],
emits: ['link-event'],
computed: { computed: {
arrayOfListItemsFromBodyText() { arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy); return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
@ -81,6 +97,7 @@ export default {
.split('- ') // At some point we'll want a better delimiter .split('- ') // At some point we'll want a better delimiter
.filter((lineItem) => lineItem); .filter((lineItem) => lineItem);
} }
return null;
}, },
textLinkEmit(copy) { textLinkEmit(copy) {
this.$parent.$emit('link-event', { this.$parent.$emit('link-event', {
@ -263,4 +280,4 @@ export default {
} }
} }
} }
</style> </style>

View file

@ -1,70 +1,91 @@
<template> <template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }"> <Form
ref="theForm"
v-slot="{ meta }"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles"> <div class="page-container-grouped-styles">
<div class="fade-on-route-transition sub-container make-tall"> <div class="fade-on-route-transition sub-container make-tall">
<siteHeader cmsWidgetName="SiteHeaderWidget" /> <siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" <siteSubHeader
justification="left" cmsWidgetName="SiteSubHeaderWidget"
issContainingPage="service-packages" justification="left"
/> issContainingPage="service-packages" />
<div class="mx-5"> <div class="mx-5">
<servicePackageQuestion ref="servicePackage" <servicePackageQuestion
cmsWidgetName="ServicePackage" ref="servicePackage"
groupName="ServicePackageQuestion" cmsWidgetName="ServicePackage"
:availableLineItems="availableLineItems" groupName="ServicePackageQuestion"
@vapsItemsSelected="vapsItemsSelectedAction" :availableLineItems="availableLineItems"
v-on:link-event="openModalAction" :validationRules="rules.optionRequired"
:validationRules="rules.optionRequired" isRequired
isRequired @vapsItemsSelected="vapsItemsSelectedAction"
/> @link-event="openModalAction" />
<p class="caption disclaimer" v-html="PriceDisclaimerText"></p> <p
<siteFooter cmsWidgetName="SiteFooterWidget" class="caption disclaimer"
:isForwardActionDisabled="!meta.valid" v-html="PriceDisclaimerText"></p>
@back-clicked="backButtonAction" <siteFooter
@ForwardClicked="forwardButtonAction" /> cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div> </div>
</div> </div>
</div> </div>
<loadingModal ref="loadingModal" /> <loadingModal ref="loadingModal" />
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" /> <contentGroupModal
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" /> ref="RainDefenseModal"
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" /> cmsWidgetName="RainDefenseModal" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" /> <contentGroupModal
ref="FrontWiperModal"
cmsWidgetName="FrontWiperModal" />
<contentGroupModal
ref="RearWiperModal"
cmsWidgetName="RearWiperModal" />
<contentGroupModal
ref="RecalModal"
cmsWidgetName="RecalModal" />
</Form> </Form>
</template> </template>
<script> <script>
// Components // Components
import baseFormMixin from '@/mixins/base-form-mixin'; import baseFormMixin from '@/mixins/base-form-mixin';
import siteHeader from '@/iss-components/site-header/site-header'; import siteHeader from '@/iss-components/site-header/site-header';
import siteFooter from '@/iss-components/site-footer/site-footer'; import siteFooter from '@/iss-components/site-footer/site-footer';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header';
import servicePackageQuestion from './service-package-question/service-package-question'; import textBlock from '@/digital-components/text-block/text-block';
import textBlock from '@/digital-components/text-block/text-block'; import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal'; import loadingModal from '@/iss-components/loading-modal/loading-modal';
import loadingModal from '@/iss-components/loading-modal/loading-modal.vue'; import { settleAllPromises } from '@/helpers/layout-helper';
import { settleAllPromises } from '@/helpers/layout-helper'; import { useMainStore } from '@/store';
import { useMainStore } from '@/store';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { required } from '@/helpers/validation-rules'; import { Form } from 'vee-validate';
import { errorMessages } from '@/constants/error-messages'; import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper';
import { Form, defineRule } from 'vee-validate';
import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper'
import globalRules from '@/constants/global-rules'; import globalRules from '@/constants/global-rules';
import servicePackageQuestion from './service-package-question/service-package-question';
const store = useMainStore(); const store = useMainStore();
export default { export default {
name: 'service-packages', name: 'service-packages',
components: {
siteHeader,
siteFooter,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
servicePackageQuestion,
loadingModal,
contentGroupModal
},
mixins: [baseFormMixin], mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage); const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const wipersPromise = await store.getWipers(); const wipersPromise = await store.getWipers();
const rainDefensePromise = await store.getRainDefense(); const rainDefensePromise = await store.getRainDefense();
const supportingItemsPromise = await store.getSupportingItems(); const supportingItemsPromise = await store.getSupportingItems();
const promiseResultMap = [ const promiseResultMap = [
{ {
@ -101,17 +122,17 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.pricedGlassParts = clonedGlassParts; vm.pricedGlassParts = clonedGlassParts;
vm.supportingItems = resultMap.supportingItems; vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults; vm.availableLineItems = pricingResults;
}); });
}, },
data() { data() {
return { return {
selectedVaps: [], selectedVaps: [],
availableLineItems: [], availableLineItems: [],
supportingItems: [], supportingItems: [],
pricedGlassParts: [], pricedGlassParts: [],
rules: { rules: {
optionRequired: globalRules.OPTION_REQUIRED optionRequired: globalRules.OPTION_REQUIRED
} }
@ -128,12 +149,12 @@ export default {
}, },
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return ( return (
store.order.serviceLocation.zipCode && store.order.serviceLocation.zipCode
store.order.serviceLocation.zipCodeCtu && && store.order.serviceLocation.zipCodeCtu
(store.order.damage.isRepair || && (store.order.damage.isRepair
(store.order.lineItems?.glassParts != null && || (store.order.lineItems?.glassParts != null
store.order.lineItems.glassParts.length > 0)) && && store.order.lineItems.glassParts.length > 0))
store.order.referralNumber?.length !== 6 && store.order.referralNumber?.length !== 6
); );
}, },
vapsItemsSelectedAction(vapsItemsSelected) { vapsItemsSelectedAction(vapsItemsSelected) {
@ -155,21 +176,11 @@ export default {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route); this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
} }
},
components: {
siteHeader,
siteFooter,
siteSubHeader,
Form, /* eslint-disable-line */
textBlock,
servicePackageQuestion,
loadingModal,
contentGroupModal
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.subheader-secondary { .subheader-secondary {
margin-top: .5rem; margin-top: .5rem;
padding: 0px; padding: 0px;