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 }],
'function-paren-newline': ['error', 'never'],
'operator-linebreak': ['error', 'before'],
'implicit-arrow-linebreak': ['error', 'beside'],
'implicit-arrow-linebreak': ['off'],
'comma-dangle': ['error', 'never'],
indent: ['error', 4],
'max-len': ['error', { code: 140 }],

View file

@ -19,9 +19,70 @@ jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn()
}));
const baseStoreGettersPageData = () => {
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(() => ({
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,
@ -50,10 +111,8 @@ const baseStoreGettersPageData = () => {
answerData: null
}
]
};
};
const baseStoreGettersDamage = () => {
return {
});
const baseStoreGettersDamage = () => ({
partsQuestionAnswers: [
{
glassLocation: 'Windshield',
@ -75,8 +134,7 @@ const baseStoreGettersDamage = () => {
]
}
]
};
};
});
useMainStore().pageData = baseStoreGettersPageData;
useMainStore().damage = baseStoreGettersDamage;
@ -99,9 +157,7 @@ describe('partQuestions.vue...', () => {
test('Should return false for valid page requisites if partsOrQuestions in pageData is missing', () => {
// Arrange
const { wrapper } = setupMocks({});
useMainStore().pageData = jest.fn(() => {
return undefined;
});
useMainStore().pageData = jest.fn(() => undefined);
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
@ -114,11 +170,9 @@ describe('partQuestions.vue...', () => {
test('Should be at least one item in partsOrQuestions', () => {
// Arrange
useMainStore().pageData = jest.fn(() => {
return {
useMainStore().pageData = jest.fn(() => ({
partsOrQuestions: []
};
});
}));
useMainStore().damage = baseStoreGettersDamage;
const { wrapper } = setupMocks({});
@ -212,13 +266,11 @@ describe('partQuestions.vue...', () => {
]
});
wrapper.vm.getParts = jest.fn(() => {
return {
wrapper.vm.getParts = jest.fn(() => ({
data: {
glassPieceParts: []
}
};
});
}));
// Act
wrapper.vm.forwardButtonAction();
@ -246,13 +298,11 @@ describe('partQuestions.vue...', () => {
}
];
useMainStore().getParts = jest.fn(() => {
return {
useMainStore().getParts = jest.fn(() => ({
data: {
glassPieceParts: []
}
};
});
}));
// Act
wrapper.vm.forwardButtonAction();
@ -278,13 +328,11 @@ describe('partQuestions.vue...', () => {
}
}
];
useMainStore().getParts = jest.fn(() => {
return {
useMainStore().getParts = jest.fn(() => ({
data: {
glassPieceParts: []
}
};
});
}));
// Act
wrapper.vm.forwardButtonAction();
@ -311,13 +359,11 @@ describe('partQuestions.vue...', () => {
}
}
];
useMainStore().getParts = jest.fn(() => {
return {
useMainStore().getParts = jest.fn(() => ({
data: {
glassPieceParts: []
}
};
});
}));
wrapper.vm.navigateForward = jest.fn();
// 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>
<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
ref="questionsPageLayout"
v-model="selectedAnswers"
:isMetaValid="meta.valid"
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData"
:validationRules="rules.optionRequired"
v-model="selectedAnswers"
:index="currentGlassIndex"
@forwardButtonAction="forwardButtonAction"
@back-click="navigateBack"
:index="currentGlassIndex" />
@back-click="navigateBack" />
</Form>
</template>
@ -30,6 +34,10 @@ import globalRules from '@/constants/global-rules';
export default {
name: 'part-questions',
components: {
Form,
questionsPageLayout
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -80,22 +88,20 @@ export default {
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.partQuestions;
glass.answerKey = glass.glassLocation + '-' + glass.glassName;
glass.answerKey = `${glass.glassLocation}-${glass.glassName}`;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
// Set up watch for each set of glass questions
this.$watch(
'selectedAnswers.' + glass.answerKey,
this.$watch(`selectedAnswers.${glass.answerKey}`,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
{ deep: true });
return updatedGlass;
});
@ -104,20 +110,18 @@ export default {
arePagePrerequisitesValid() {
const partQuestionsFromPageData = useMainStore().pageData(issPageValues.PART_QUESTIONS);
return (
partQuestionsFromPageData &&
Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0
partQuestionsFromPageData
&& Object.keys(partQuestionsFromPageData.partsOrQuestions).length > 0
);
},
async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => {
return {
const questionAnswersArray = this.questionsData.map((glass) => ({
glassLocation: glass.glassLocation,
glassName: glass.glassName,
result: (glass.answerData && glass.answerData.answerResult) || '',
answeredQuestions: glass.answerData?.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart
};
});
}));
// clear out answerData for future page loads; must occur prior to store save
this.questionsData.forEach((glass) => {
@ -137,10 +141,6 @@ export default {
// TODO KO delete for quote mvp
this.navigateForward(glassPartsForStore, null);
}
},
components: {
Form,
questionsPageLayout
}
};
</script>

View file

@ -1,17 +1,20 @@
<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="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 payment">
<p>Placeholder for payment page</p>
<siteFooter
cmsWidgetName="SiteFooterWidget"
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction"
/>
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -31,14 +34,12 @@ import { useMainStore } from '@/store';
export default {
name: 'payment-page',
components: {
siteHeader,
siteFooter,
Form
},
mixins: [BaseFormMixin],
data() {
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -51,11 +52,18 @@ export default {
}];
// use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap);
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
},
methods:
{
@ -69,17 +77,11 @@ export default {
navigateForward() {
}
},
components: {
siteHeader,
siteFooter,
Form,
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.payment p{
margin-bottom:0.75rem;
}
</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
import { shallowMount } from '@vue/test-utils';
import { settleAllPromises } from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
@ -19,6 +18,55 @@ jest.mock('@/helpers/cms-content-helper', () => ({
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', () => {
test('Should render policy-holder-details sub-components (policy holder first name, last name, street address etc.)', async () => {
// Arrange
@ -116,65 +164,10 @@ describe('navigation', () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
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>
<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="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 px-5">
<div class="row mt-4 mb-3">
<div class="col">
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" id="sub-header"/>
<addressQuestions ref="addressQuestions" v-model="customerQuestions.addressQuestions" includeStreetAddress2="true" id="address-questions-wrapper"/>
<siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeaderWidget" />
<addressQuestions
id="address-questions-wrapper"
ref="addressQuestions"
v-model="customerQuestions.addressQuestions"
includeStreetAddress2="true" />
<textboxQuestion
ref="policyHolderFirstName"
v-model="customerQuestions.firstName"
inputId="firstNameField"
cmsWidgetName="PolicyholderFirstNameQuestion"
v-model="customerQuestions.firstName"
isRequired
ref="policyHolderFirstName"
disableAutoFill
:validationRules="rules.firstName" />
<textboxQuestion
ref="policyHolderLastName"
v-model="customerQuestions.lastName"
class="mt-4"
inputId="lastNameField"
cmsWidgetName="PolicyholderLastNameQuestion"
v-model="customerQuestions.lastName"
isRequired
ref="policyHolderLastName"
disableAutoFill
:validationRules="rules.lastName" />
</div>
</div>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
ref="siteFooter"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction"
/>
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -58,21 +67,16 @@ import { useMainStore } from '@/store';
export default {
name: 'policy-holder-details',
components: {
siteHeader,
siteSubHeader,
textboxQuestion,
addressQuestions,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
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) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -86,12 +90,34 @@ export default {
];
// use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap);
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
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:
{
backButtonAction() {
@ -105,18 +131,14 @@ export default {
navigateForward() {
if (this.vehiclesCount > 0) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
this.$route,
{},
{},
this.vehiclesFound
);
this.vehiclesFound);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
this.$route);
}
},
@ -132,29 +154,13 @@ export default {
},
firstName: this.mainStore.order.customer.firstName,
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>
<style lang="scss">
<style lang="scss" scoped>
#sub-header p {
font-size: 16px;
line-height: 26px;

View file

@ -1,6 +1,12 @@
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';
const mockMixin = {
methods: {
getCmsContent: jest.fn(() => ['vehicle not listed'])
}
};
describe('policy-vehicles-question.vue', () => {
test('Selected vehicle is emitted upon selection', async () => {
// Arrange
@ -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" />
</template>
<script>
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question';
export default ({
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 { shallowMount } from '@vue/test-utils';
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 { endorsementOptions } from '@/constants/endorsement-options';
import { createTestingPinia } from '@pinia/testing';
import { issPageValues } from "@/router/router-constants/issPage-values";
import { vehicleSelectionOptions } from "@/constants/vehicle-selection-options";
import { issPageValues } from '@/router/router-constants/issPage-values';
import { vehicleSelectionOptions } from '@/constants/vehicle-selection-options';
// Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({
@ -27,6 +27,54 @@ jest.mock('@/helpers/layout-helper.js', () => ({
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', () => {
test('Should navigate to CLICKED_BACK if backButtonAction is run', async () => {
// Arrange
@ -48,7 +96,7 @@ describe('policy-vehicles.vue', () => {
await wrapper.setData({
selectedVehicleVin: vin,
policyVehicles: [
{ vin: vin },
{ vin }
],
bailout: false
});
@ -56,19 +104,19 @@ describe('policy-vehicles.vue', () => {
const year = getRandomInt(1998, 2023);
const lookupVehicleResponse = {
data: {
year: year,
year
}
};
const store = useMainStore();
store.lookupVehicleByVin.mockReturnValue(Promise.resolve(lookupVehicleResponse));
const expectedInput = {
year: year,
vin: vin,
year,
vin,
noCoverage: true,
deductible: 0,
repairWaived: false
}
};
// Act
await wrapper.vm.forwardButtonAction();
@ -76,12 +124,10 @@ describe('policy-vehicles.vue', () => {
// Assert
expect(wrapper.vm.bailout).toBeFalsy();
expect(store.updateVehicle).toHaveBeenCalledWith(expectedInput);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
undefined,
{},
{}
);
{});
});
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
expect(wrapper.vm.bailout).toBeTruthy();
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
undefined,
{},
{}
);
{});
});
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();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
undefined,
{},
{}
);
{});
});
});
@ -209,7 +251,7 @@ describe('policy-vehicles.vue', () => {
vin: otherVin
}
]
}
};
// Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -225,11 +267,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin,
policyVehicles: [
{
vin: vin,
vin,
coverages: []
}
]
}
};
// Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -247,7 +289,7 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin,
policyVehicles: [
{
vin: vin,
vin,
coverages: [
{
deductible: firstDeductible
@ -258,7 +300,7 @@ describe('policy-vehicles.vue', () => {
]
}
]
}
};
// Act
const result = policyVehicles.computed.deductibleForSelectedVehicle.call(testValues);
@ -280,7 +322,7 @@ describe('policy-vehicles.vue', () => {
vin: otherVin
}
]
}
};
// Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -296,11 +338,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin,
policyVehicles: [
{
vin: vin,
vin,
endorsements: []
}
],
}
]
};
// Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -316,11 +358,11 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin,
policyVehicles: [
{
vin: vin,
vin,
endorsements: [endorsementOptions.EDUCATOR, endorsementOptions.PARKING_GUARD]
}
]
}
};
// Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -336,7 +378,7 @@ describe('policy-vehicles.vue', () => {
selectedVehicleVin: vin,
policyVehicles: [
{
vin: vin,
vin,
endorsements: [
endorsementOptions.EDUCATOR,
endorsementOptions.REPAIR_WAIVED,
@ -344,7 +386,7 @@ describe('policy-vehicles.vue', () => {
]
}
]
}
};
// Act
const result = policyVehicles.computed.repairWaivedForSelectedVehicle.call(testValues);
@ -359,7 +401,7 @@ describe('policy-vehicles.vue', () => {
const vin = getRandomString(17, 17);
useMainStore().applicationUser = {
pageData: {
[issPageValues.POLICY_VEHICLES]: [{ vin: vin }],
[issPageValues.POLICY_VEHICLES]: [{ vin }]
}
};
@ -372,51 +414,3 @@ describe('policy-vehicles.vue', () => {
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 providerPrefRadio from './provider-pref-radio';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
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);
});
});
import providerPrefRadio from './provider-pref-radio';
const mockProps = {
buttonLabel: 'buttonLabel test copy',
@ -125,3 +18,109 @@ function setupMocks({ mountOptionsMockData = {} }) {
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>
<baseInputButton v-bind="$props" v-model="selectedValue">
<div class="option-label"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
<baseInputButton
v-bind="$props"
v-model="selectedValue">
<div
class="option-label"
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="option-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="option-info" v-html="this.buttonAuxiliaryCopy"></span>
<span v-html="buttonLabel"></span>
<span
class="option-info"
v-html="buttonAuxiliaryCopy"></span>
</p>
<p class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy"></p>
<div>
<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 -->
<span v-html="listItem"></span>
</li>
@ -26,16 +35,14 @@
<script>
import baseInputButton from '@/digital-components/base-input-button/base-input-button';
import textLink from '@/ux-components/text-link/text-link';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
export default {
name: 'providerPrefRadio',
mixins: [inputButtonWrapperMixin],
name: 'provider-pref-radio',
components: {
baseInputButton,
textLink
baseInputButton
},
mixins: [inputButtonWrapperMixin],
computed: {
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
@ -48,6 +55,7 @@ export default {
.split('- ') // At some point we'll want a better delimiter
.filter((lineItem) => lineItem);
}
return null;
}
}
};

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 { 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 { useMainStore } from '@/store';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
@ -18,6 +18,29 @@ jest.mock('@/helpers/cms-content-helper', () => ({
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', () => {
test('Should navigate to safelite flow when safelite selected', () => {
// Arrange
@ -57,30 +80,3 @@ describe('provider-preference.vue', () => {
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>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
<Form
ref="theForm"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeader" id="sub-header" class="mb-5 mt-4" />
<buttonQuestion ref="buttonQuestion"
<siteSubHeader
id="sub-header"
cmsWidgetName="SiteSubHeader"
class="mb-5 mt-4" />
<buttonQuestion
ref="buttonQuestion"
v-model="selectedProvider"
questionText="Select an option:"
:answers="prefAnswers"
groupName="prefQuestions"
buttonTypeString="providerPrefRadio"
v-model="selectedProvider"
:validationRules="rules.optionRequired"
isRequired
class="mx-5" />
<div class="px-5">
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction"
ref="siteFooter" />
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
<recalModal ref="recalModal" cmsWidgetName="RecalModal" />
<steeringModal cmsWidgetName="StateSteeringModal" ref="StateSteeringModal" />
<recalModal
ref="recalModal"
cmsWidgetName="RecalModal" />
<steeringModal
ref="StateSteeringModal"
cmsWidgetName="StateSteeringModal" />
<shopPreferenceModal
cmsWidgetName="ShopPreferenceDrawer"
ref="ShopPreferenceDrawer"
@openSteering="openStateSteeringModal"
:showSteeringLink="showSteeringLink"/>
<tpaRecalModal cmsWidgetName="TPARecalModal"
cmsWidgetName="ShopPreferenceDrawer"
:showSteeringLink="showSteeringLink"
@openSteering="openStateSteeringModal" />
<tpaRecalModal
ref="TPARecalModal"
cmsWidgetName="TPARecalModal"
:ackError="ackError"
@buttonClick="navigateWithTPAAck"
/>
@buttonClick="navigateWithTPAAck" />
</Form>
</template>
<script>
// 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 { errorMessages } from '@/constants/error-messages';
import buttonQuestion from '@/digital-components/button-question/button-question';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal';
import { issPageValues } from '@/router/router-constants/issPage-values';
// Import Component
@ -55,42 +63,30 @@ import { Form } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer';
import siteHeader from '@/iss-components/site-header/site-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 steeringModal from '@/layouts/provider-preference/steering-modal/steering-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';
const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' };
export default {
name: 'provider-preference',
mixins: [baseFormMixin],
components: {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
buttonQuestion,
contentGroupModal,
providerPrefRadio,
recalModal,
steeringModal,
shopPreferenceModal,
tpaRecalModal
},
data() {
return {
selectedProvider: null,
showSteeringLink: false,
tpaAcknowledgement: false,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
@ -110,6 +106,16 @@ export default {
}
});
},
data() {
return {
selectedProvider: null,
showSteeringLink: false,
tpaAcknowledgement: false,
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
},
computed: {
isForwardActionDisabled() {
return this.selectedProvider === null;
@ -140,6 +146,12 @@ export default {
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: {
getHeaderTextFromCms(cmsWidgetName) {
const headerText = this.getCmsContent(cmsWidgetName, 'HeaderText');
@ -161,7 +173,10 @@ export default {
this.$router.navigate(scenario, this.$route);
},
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);
},
forwardButtonAction() {
@ -183,24 +198,22 @@ export default {
scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED;
}
break;
};
default:
}
this.navigateForward(scenario);
this.mainStore.saveProviderPreferenceData({selectedProvider: this.selectedProvider, tpaAcknowledgement: this.tpaAcknowledgement});
this.mainStore.saveProviderPreferenceData({
selectedProvider: this.selectedProvider,
tpaAcknowledgement: this.tpaAcknowledgement
});
}
},
openStateSteeringModal() {
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>
<style lang="scss" >
<style lang="scss" scoped>
#sub-header span{
color: $black;
}

View file

@ -1,9 +1,8 @@
import shopPreferenceModal from './shop-preference-modal.vue'
import { shallowMount } from '@vue/test-utils';
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 shopPreferenceModal from './shop-preference-modal';
// Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => ({
@ -16,6 +15,26 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn()
}));
function setupMocks(propsData) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
mountOptions.propsData = propsData;
const wrapper = shallowMount(shopPreferenceModal,
mountOptions);
const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => { });
return { wrapper };
}
describe('provider-preference.vue', () => {
test('Should display state specific steering modal link when in state with steering language', async () => {
// Arrange
@ -25,7 +44,6 @@ describe('provider-preference.vue', () => {
// Test
expect(wrapper.vm.showSteeringLink).toBeTruthy();
});
test('Should not display state specific steering modal link when not in state with steering language', async () => {
@ -38,25 +56,3 @@ describe('provider-preference.vue', () => {
expect(wrapper.vm.showSteeringLink).toBeFalsy();
});
});
function setupMocks(propsData) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
mountOptions.propsData = propsData;
const wrapper = shallowMount(
shopPreferenceModal,
mountOptions
);
const apiResponses = {};
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation (() => {});
return { wrapper };
}

View file

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

View file

@ -1,10 +1,9 @@
import steeringModal from './steering-modal.vue'
import { shallowMount } from '@vue/test-utils';
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 { useMainStore } from '@/store';
import steeringModal from './steering-modal';
// Mock our module for promises.
jest.mock('@/helpers/layout-helper.js', () => ({
@ -17,15 +16,6 @@ jest.mock('@/helpers/cms-content-helper', () => ({
setupModalLinks: jest.fn()
}));
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();
})
});
function setupMocks() {
const mountOptions = getMountOptions({
router: {
@ -33,10 +23,8 @@ function setupMocks(){
}
});
const wrapper = shallowMount(
steeringModal,
mountOptions
);
const wrapper = shallowMount(steeringModal,
mountOptions);
const apiResponses = {};
@ -45,3 +33,12 @@ function setupMocks(){
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"
:footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick">
<h5 class="text-center mb-4">{{ModalHeadline}}</h5>
<h5 class="text-center mb-4">
{{ ModalHeadline }}
</h5>
<div>
<div class="mb-4" v-html="ModalBodyText"></div>
<div v-if="ModalSubBodyText" v-html="ModalSubBodyText"></div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<div
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></div>
</div>
</modal>
</template>
@ -19,6 +25,9 @@ import { states } from '@/constants/states';
export default {
name: 'content-group-modal',
components: {
modal
},
props: {
cmsWidgetName: String
},
@ -27,8 +36,8 @@ export default {
return this.cmsWidgetName;
},
ModalHeadline() {
let header = this.getCmsContent(this.cmsWidgetName, 'HeaderText');
return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state])
const header = this.getCmsContent(this.cmsWidgetName, 'HeaderText');
return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]);
},
ModalSubheadertext() {
return this.getCmsContent(this.cmsWidgetName, 'SubheaderText');
@ -60,14 +69,11 @@ export default {
getStateSpecificText(value) {
return this.mainStore.order.customer.address.state?.toLowerCase() == value?.toLowerCase();
}
},
components: {
modal
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.modal {
&.modal-component {
.modal-dialog {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,24 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import serviceLocation from '@/layouts/service-location/service-location.vue';
import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper';
import serviceLocation from '@/layouts/service-location/service-location';
// Define Mocks
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(() => {
return Promise.resolve('content');
})
fetchCmsContentForPage: jest.fn(() => Promise.resolve('content'))
}));
const mockGetServiceabilityDetails = (mockServiceZipCode) => {
function setupMocks() {
const wrapper = shallowMount(serviceLocation,
getMountOptions({
router: {
navigate: jest.fn()
}
}));
return { wrapper };
}
const mockGetServiceabilityDetails = () => {
const serviceabilityDetails = {
isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true,
@ -21,18 +29,15 @@ const mockGetServiceabilityDetails = (mockServiceZipCode) => {
return Promise.resolve(serviceabilityDetails);
};
jest.mock(
'@/helpers/service-location-helper',
jest.mock('@/helpers/service-location-helper',
() => ({
getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
return mockGetServiceabilityDetails(mockServiceZipCode);
})
})
);
getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode))
}));
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
// linkWidgetName is not defined. This whole spec file needs review.
if (widgetName === linkWidgetName) {
return mockLinkCmsContent[cmsFieldName];
}
@ -130,7 +135,7 @@ describe('updating service zip', () => {
const newServiceZipCodeQuestion = {
zipCode: '45433',
state: 'OH',
state: 'OH'
};
// Act
@ -140,16 +145,3 @@ describe('updating service zip', () => {
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>
<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="fade-on-route-transition position-relative">
<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="container-fluid pb-2">
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<serviceZipModalQuestion
v-model="serviceZipCodeQuestion"
ref="serviceZipCodeQuestion"
v-model="serviceZipCodeQuestion"
modalWidgetName="ServiceZipModalWidget"
@updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase"
modalWidgetName="ServiceZipModalWidget" />
@updated-contains-military-base="setContainsMilitaryBase" />
<alert
v-if="displayMilitaryZipAlert"
ref="alertMilitaryBaseZip"
class="my-5"
cmsWidgetName="AlertMilitaryBaseZipWidget"
v-if="displayMilitaryZipAlert"
alertClass="alert-warning" />
<buttonQuestion
v-model="selectedAppointmentType"
cmsWidgetName="ServiceTypeQuestionWidget"
:questionText="questionText"
:answers="answersFromCms"
buttonTypeString="listCard"
isRequired
v-model="selectedAppointmentType"
validationRules="selection-required"
class="service-location-button-question mb-1" />
<div class="select-car">
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction"
ref="siteFooter"
/>
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -62,17 +68,26 @@ import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-loca
// Import Component
import alert from '@/ux-components/alert/alert';
import baseFormMixin from '@/mixins/base-form-mixin';
import baseMixin from '@/mixins/base-mixin';
import { Form, defineRule } from 'vee-validate';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue';
import siteFooter from '@/iss-components/site-footer/site-footer';
import siteHeader from '@/iss-components/site-header/site-header';
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';
// DEFINE VALIDATION RULES
defineRule('selection-required', required(errorMessages.OPTION_REQUIRED));
export default {
name: 'service-location',
components: {
siteFooter,
siteHeader,
siteSubHeader,
buttonQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
serviceZipModalQuestion,
alert
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -103,10 +118,8 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.setData(
resultMap.zipCodeData,
resultMap.serviceabilityDetails
)
vm.setData(resultMap.zipCodeData,
resultMap.serviceabilityDetails);
});
},
setup() {
@ -132,25 +145,25 @@ export default {
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
},
serviceZipCodeQuestion: {
get: function() {
get() {
return {
zipCode: this.zipCode,
state: this.state
};
},
set: function (newValue) {
set(newValue) {
this.zipCode = newValue.zipCode;
this.state = newValue.state;
// TODO: review - this seems like it should be awaited.
this.$nextTick();
}
},
isServiceableMobile() {
if (this.isRecalibrationServiceableMobile !== null) {
return this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile;
} else {
return this.isGlassServiceableMobile;
}
return this.isGlassServiceableMobile;
},
displayMilitaryZipAlert() {
return this.zipContainsMilitaryBase && this.isServiceableMobile;
@ -188,26 +201,17 @@ export default {
},
setServiceabilityDetails(serviceabilityDetails) {
this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop;
this.isRecalibrationServiceableInshop =
serviceabilityDetails.isRecalibrationServiceableInshop;
this.isRecalibrationServiceableInshop
= serviceabilityDetails.isRecalibrationServiceableInshop;
this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile;
this.isRecalibrationServiceableMobile
= serviceabilityDetails.isRecalibrationServiceableMobile;
}
},
components: {
siteFooter,
siteHeader,
siteSubHeader,
buttonQuestion,
Form,
serviceZipModalQuestion,
alert
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.page-container-grouped-styles {
overflow: auto;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +1,38 @@
export const allGlassPartsAndItemsHavePrices = (lineItems) => {
if (lineItems.glassParts && Array.isArray(lineItems.glassParts)) {
if (!processLineItems(lineItems.glassParts))
{
return false;
}
};
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 hasPrice = (lineItem) => {
const value = (!!lineItem?.kitPrice && lineItem.kitPrice !== 0)
|| (!!lineItem?.laborAmount && lineItem.laborAmount !== 0)
|| (!!lineItem?.sellingPrice && lineItem.sellingPrice !== 0);
return value;
};
const processLineItems = (lineItems) => {
let returnVal = true;
lineItems.forEach( lineItem => {
lineItems.forEach((lineItem) => {
if (!hasPrice(lineItem)) {
returnVal = false;
};
}
});
return returnVal;
};
export const allGlassPartsAndItemsHavePrices = (lineItems) => {
if (lineItems.glassParts && Array.isArray(lineItems.glassParts)) {
if (!processLineItems(lineItems.glassParts)) {
return false;
}
}
const hasPrice = (lineItem) => {
const value = (!!lineItem?.kitPrice && lineItem.kitPrice != 0) ||
(!!lineItem?.laborAmount && lineItem.laborAmount != 0) ||
(!!lineItem?.sellingPrice && lineItem.sellingPrice != 0);
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', () => {
// Arrange
const mockData = { glassParts: [{partNumber: 'DW01571GBNNOEE', sellingPrice: 25}], supportingItems: [{ laborAmount: 0 }] }
const mockData = { glassParts: [{ partNumber: 'DW01571GBNNOEE', sellingPrice: 25 }], supportingItems: [{ laborAmount: 0 }] };
// Act
const actual = allGlassPartsAndItemsHavePrices(mockData);
// 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', () => {
// Arrange
const mockData = { glassParts: [{partNumber: 'DW01571GBNNOEE', sellingPrice: 25}], laborAmount: [{ sellingPrice: 10 }] }
const mockData = { glassParts: [{ partNumber: 'DW01571GBNNOEE', sellingPrice: 25 }], laborAmount: [{ sellingPrice: 10 }] };
// Act
const actual = allGlassPartsAndItemsHavePrices(mockData);
// Assert

View file

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

View file

@ -1,113 +1,6 @@
import { mount } from '@vue/test-utils';
import servicePackageRadio from './service-package-radio';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
describe('service-package-radio.vue', () => {
it('Should include buttonLabel in html', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabel']));
});
it('Should include buttonLabelAuxillaryCopy in html', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabelAuxillaryCopy']));
});
it('Should include buttonLabelSubCopy in html', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonLabelSubCopy']));
});
it('Should include buttonFooterCopy in html', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps['buttonFooterCopy']));
});
it('Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a dashed (x5) buttonBodyCopy', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
it('Should get strings from getArrayOfListItemsFromRawCmsCopy without dashes when provided with a dashed buttonBodyCopy', async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
const fileredResults = results.filter((result) => {
return result.includes(' -');
});
expect(fileredResults.length).toBe(0);
});
it('Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total dashes, but one is empty ', async () => {
// Arrange
const moddedProps = mockProps;
moddedProps['buttonBodyCopy'] = '- buttonBodyCopy test copy - 2 - 3 - 4 - 5 -';
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps
}
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
});
import servicePackageRadio from './service-package-radio';
const mockProps = {
buttonLabel: 'buttonLabel test copy',
@ -125,3 +18,109 @@ function setupMocks({ mountOptionsMockData = {} }) {
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>
<baseInputButton v-bind="$props" v-model="selectedValue">
<div class="package-label mb-4"
:class="[this.buttonLabelSubCopy ? 'has-subheader' : '']"
<baseInputButton
v-bind="$props"
v-model="selectedValue">
<div
class="package-label mb-4"
:class="[buttonLabelSubCopy ? 'has-subheader' : '']"
for="testradio">
<div class="package-specs">
<p class="m-0">
<span v-html="this.buttonLabel"></span>
<span class="pricing-info" v-html="this.buttonAuxiliaryCopy"></span>
<span v-html="buttonLabel"></span>
<span
class="pricing-info"
v-html="buttonAuxiliaryCopy"></span>
</p>
<p class="sub-label m-0"
v-if="this.buttonLabelSubCopy"
v-html="this.buttonLabelSubCopy"></p>
<p
v-if="buttonLabelSubCopy"
class="sub-label m-0"
v-html="buttonLabelSubCopy"></p>
<div>
<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 -->
<span v-if="!doesCopyContainTextLink(listItem)"
<span
v-if="!doesCopyContainTextLink(listItem)"
v-html="listItem"></span>
<!-- with textLink -->
<template v-else
<template
v-for="copy in splitCopyOnCMSPlaceHolder(listItem)"
v-else
:key="copy">
<span v-if="!doesCopyContainTextLink(copy)" v-html="copy"></span>
<span
v-if="!doesCopyContainTextLink(copy)"
v-html="copy"></span>
<span v-else>
<textLink linkType="text"
<textLink
linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
@click-event="textLinkEmit(copy)"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
aria-label="Modal window" />
aria-label="Modal window"
@click-event="textLinkEmit(copy)" />
</span>
</template>
</li>
</ul>
<!-- End of body text parsing -->
<!--ms-n6-->
<div class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-if="this.buttonFooterCopy"
v-html="this.buttonFooterCopy"></div>
<div
v-if="buttonFooterCopy"
class="package-footer fw-bold caption mt-4 ml-n4 mr-3"
v-html="buttonFooterCopy"></div>
</div>
</div>
</div>
@ -54,14 +69,15 @@ import {
splitCopyOnCMSPlaceHolder,
doesCopyContainTextLink
} from '@/helpers/cms-content-helper';
export default {
name: 'servicePackageRadio',
emits:['link-event'],
mixins: [inputButtonWrapperMixin],
name: 'service-package-radio',
components: {
baseInputButton,
textLink
},
mixins: [inputButtonWrapperMixin],
emits: ['link-event'],
computed: {
arrayOfListItemsFromBodyText() {
return this.getArrayOfListItemsFromRawCmsCopy(this.buttonBodyCopy);
@ -81,6 +97,7 @@ export default {
.split('- ') // At some point we'll want a better delimiter
.filter((lineItem) => lineItem);
}
return null;
},
textLinkEmit(copy) {
this.$parent.$emit('link-event', {

View file

@ -1,24 +1,31 @@
<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="fade-on-route-transition sub-container make-tall">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget"
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
justification="left"
issContainingPage="service-packages"
/>
issContainingPage="service-packages" />
<div class="mx-5">
<servicePackageQuestion ref="servicePackage"
<servicePackageQuestion
ref="servicePackage"
cmsWidgetName="ServicePackage"
groupName="ServicePackageQuestion"
:availableLineItems="availableLineItems"
@vapsItemsSelected="vapsItemsSelectedAction"
v-on:link-event="openModalAction"
:validationRules="rules.optionRequired"
isRequired
/>
<p class="caption disclaimer" v-html="PriceDisclaimerText"></p>
<siteFooter cmsWidgetName="SiteFooterWidget"
@vapsItemsSelected="vapsItemsSelectedAction"
@link-event="openModalAction" />
<p
class="caption disclaimer"
v-html="PriceDisclaimerText"></p>
<siteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
@ -26,12 +33,18 @@
</div>
</div>
<loadingModal ref="loadingModal" />
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
<contentGroupModal
ref="RainDefenseModal"
cmsWidgetName="RainDefenseModal" />
<contentGroupModal
ref="FrontWiperModal"
cmsWidgetName="FrontWiperModal" />
<contentGroupModal
ref="RearWiperModal"
cmsWidgetName="RearWiperModal" />
<contentGroupModal
ref="RecalModal"
cmsWidgetName="RecalModal" />
</Form>
</template>
@ -41,23 +54,31 @@ import baseFormMixin from '@/mixins/base-form-mixin';
import siteHeader from '@/iss-components/site-header/site-header';
import siteFooter from '@/iss-components/site-footer/site-footer';
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 contentGroupModal from '@/iss-components/content-group-modal/content-group-modal';
import loadingModal from '@/iss-components/loading-modal/loading-modal.vue';
import loadingModal from '@/iss-components/loading-modal/loading-modal';
import { settleAllPromises } from '@/helpers/layout-helper';
import { useMainStore } from '@/store';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { required } from '@/helpers/validation-rules';
import { errorMessages } from '@/constants/error-messages';
import { Form, defineRule } from 'vee-validate';
import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper'
import { Form } from 'vee-validate';
import { allGlassPartsAndItemsHavePrices } from '@/layouts/service-packages/service-package-helper/service-package-helper';
import globalRules from '@/constants/global-rules';
import servicePackageQuestion from './service-package-question/service-package-question';
const store = useMainStore();
export default {
name: 'service-packages',
components: {
siteHeader,
siteFooter,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
servicePackageQuestion,
loadingModal,
contentGroupModal
},
mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -128,12 +149,12 @@ export default {
},
arePagePrerequisitesValid() {
return (
store.order.serviceLocation.zipCode &&
store.order.serviceLocation.zipCodeCtu &&
(store.order.damage.isRepair ||
(store.order.lineItems?.glassParts != null &&
store.order.lineItems.glassParts.length > 0)) &&
store.order.referralNumber?.length !== 6
store.order.serviceLocation.zipCode
&& store.order.serviceLocation.zipCodeCtu
&& (store.order.damage.isRepair
|| (store.order.lineItems?.glassParts != null
&& store.order.lineItems.glassParts.length > 0))
&& store.order.referralNumber?.length !== 6
);
},
vapsItemsSelectedAction(vapsItemsSelected) {
@ -155,21 +176,11 @@ export default {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
}
},
components: {
siteHeader,
siteFooter,
siteSubHeader,
Form, /* eslint-disable-line */
textBlock,
servicePackageQuestion,
loadingModal,
contentGroupModal
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.subheader-secondary {
margin-top: .5rem;
padding: 0px;