Merge pull request #383 from Safelite/refactor/linting8

Baseline linting for the remainder of the layout pages and the router
This commit is contained in:
DavidAtSafelite 2023-07-31 13:04:13 -04:00 committed by GitHub
commit 9b0c54bf5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1095 additions and 1189 deletions

View file

@ -5,7 +5,7 @@ const errorMessages = {
PASSENGER_SIDE_OPTIONS_REQUIRED: 'Please select window',
WINDSHIELD_DAMAGE_TYPE_REQUIRED: 'Please select windshield damage',
WINDSHIELD_CHIP_COUNT_REQUIRED: 'Please select chip(s)',
WINSHIELD_REPLACE_OPTIONS_REQUIRED: 'Please select windshield part',
WINDSHIELD_REPLACE_OPTIONS_REQUIRED: 'Please select windshield part',
REPLACE_OPTIONS_REQUIRED: 'Please select rear window type',
STREET_ADDRESS_REQUIRED: 'Please enter your street address',
CITY_REQUIRED: 'Please enter your city',

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"/>
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 confirmation">
<p>Placeholder for tpa-confrimation page</p>
<p>Placeholder for tpa-confirmation page</p>
<siteFooter
cmsWidgetName="SiteFooterWidget"
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction"
/>
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -27,15 +30,15 @@ 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: 'tpa-confirmation',
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);
@ -46,11 +49,17 @@ 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);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
},
methods:
{
backButtonAction() {
@ -61,20 +70,13 @@ export default {
},
navigateForward() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
this.$route);
}
},
components: {
siteHeader,
siteFooter,
Form
}
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.confirmation p{
margin-bottom:0.75rem;

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"/>
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 search">
<p>Placeholder for TPA-Search</p>
<siteFooter
cmsWidgetName="SiteFooterWidget"
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction"
/>
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -29,9 +32,13 @@ import BaseFormMixin from '@/mixins/base-form-mixin.js';
export default {
name: 'tpa-search',
mixins: [BaseFormMixin],
data() {
components: {
siteHeader,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -42,33 +49,30 @@ 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);
});
},
data() {
},
methods:
{
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
return this.navigateForward();
},
navigateForward() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
this.$route);
}
},
components: {
siteHeader,
siteFooter,
Form,
}
}
</script><style lang="scss">
}
};
</script>
<style lang="scss" scoped>
.search p{
margin-bottom:0.75rem;
}

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"/>
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2 submit">
<p>Placeholder for TPA-Submit</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: 'tpa-submit',
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:
{
@ -68,20 +76,13 @@ export default {
},
navigateForward() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
this.$route);
}
},
components: {
siteHeader,
siteFooter,
Form
}
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.submit {
margin-bottom:0.75rem;

View file

@ -33,11 +33,9 @@ describe('damage-location-question.vue', () => {
});
// Act
damageLocationQuestion.methods.initializeComponent.call(
wrapper.vm,
damageLocationQuestion.methods.initializeComponent.call(wrapper.vm,
damageOptions,
'car-group'
);
'car-group');
// Assert
expect(wrapper.vm.damageOptions).toStrictEqual({
@ -66,7 +64,7 @@ function setupMocks({
};
mountOptions.propsData = {
modelValue: modelValueProp,
isMultiSelect: isMultiSelect
isMultiSelect
};
mountOptions.mixins = [mockMixin];
@ -74,7 +72,7 @@ function setupMocks({
// Mock CMS content
const cmsContent = {
groupName: groupName,
groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers
};

View file

@ -1,13 +1,13 @@
<template>
<div class="damage-location-question">
<buttonQuestion
v-model="selectedValues"
:questionText="questionText"
isMultiSelect
:answers="answersToDisplay"
:groupName="groupName"
buttonTypeString="listCard"
isRequired
v-model="selectedValues"
validationRules="damage-location-required" />
</div>
</template>
@ -22,21 +22,20 @@ import { errorMessages } from '@/constants/error-messages';
defineRule('damage-location-required', required(errorMessages.DAMAGE_LOCATION_REQUIRED));
export default {
name: 'damageLocationQuestion',
data() {
return {
damageOptions: Object
};
name: 'damage-location-question',
components: {
buttonQuestion
},
props: {
modelValue: Array,
groupName: String,
cmsWidgetName: String
},
methods: {
initializeComponent(damageOptions) {
this.damageOptions = damageOptions;
}
emits: ['update:modelValue'],
data() {
return {
damageOptions: Object
};
},
computed: {
questionText() {
@ -46,10 +45,10 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
this.$emit('update:modelValue', newValue);
}
},
@ -57,8 +56,8 @@ export default {
return {
Windshield: true,
SideDoor:
this.damageOptions.driverSideOptions.availableReplacementOptions.length ||
this.damageOptions.passengerSideOptions.availableReplacementOptions.length,
this.damageOptions.driverSideOptions.availableReplacementOptions.length
|| this.damageOptions.passengerSideOptions.availableReplacementOptions.length,
RearWindow: this.damageOptions.backGlassOptions.availableReplacementOptions.length
};
},
@ -67,8 +66,8 @@ export default {
? this.answersFromCms.filter((ans) => {
const name = ans.Name.split('-');
return (
name[0].toUpperCase() === this.mainStore.vehicle.category &&
this.damageOptionsMap[name[1]]
name[0].toUpperCase() === this.mainStore.vehicle.category
&& this.damageOptionsMap[name[1]]
);
})
: [];
@ -79,8 +78,10 @@ export default {
});
}
},
components: {
buttonQuestion
methods: {
initializeComponent(damageOptions) {
this.damageOptions = damageOptions;
}
}
};
</script>

View file

@ -26,11 +26,9 @@ describe('replace-options-question.vue', () => {
});
// Act
replaceOptionsQuestion.methods.initializeComponent.call(
wrapper.vm,
replaceOptionsQuestion.methods.initializeComponent.call(wrapper.vm,
replaceOptions,
'car-group'
);
'car-group');
// Assert
expect(wrapper.vm.replaceOptions).toStrictEqual(['Windshield', 'FrontDoor']);
@ -71,12 +69,10 @@ describe('replace-options-question.vue', () => {
});
// Act
wrapper.vm.$options.methods.initializeComponent.call(
wrapper.vm,
wrapper.vm.$options.methods.initializeComponent.call(wrapper.vm,
cmsContent,
replaceOptions,
'car-group'
);
'car-group');
wrapper.vm.$options.watch.isAvailable.call(wrapper.vm, true);
// Assert
@ -106,9 +102,9 @@ function setupMocks({
};
mountOptions.propsData = {
modelValue: modelValueProp,
isAvailable: isAvailable,
isMultiSelect: isMultiSelect,
filterByVehicleCategory: filterByVehicleCategory,
isAvailable,
isMultiSelect,
filterByVehicleCategory
};
mountOptions.mixins = [mockMixin];
@ -121,7 +117,7 @@ function setupMocks({
// Mock CMS content
const cmsContent = {
groupName: groupName,
groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers
};

View file

@ -1,18 +1,20 @@
<template>
<transition name="fade" mode="out-in">
<transition
name="fade"
mode="out-in">
<div
v-if="shouldDisplayReplaceOptionsQuestion"
class="replace-options-question"
:class="this.answersToDisplay.length < 2 ? 'd-none' : ''"
:class="answersToDisplay.length < 2 ? 'd-none' : ''"
aria-live="polite">
<buttonQuestion
v-model="selectedValues"
isWide
:questionText="questionText"
:isMultiSelect="isMultiSelect"
:answers="answersToDisplay"
:groupName="groupName"
buttonTypeString="listCard"
v-model="selectedValues"
:validationRules="validationRules"
:suppressError="suppressError"
:isRequired="isRequired" />
@ -24,11 +26,9 @@
import buttonQuestion from '@/digital-components/button-question/button-question';
export default {
name: 'replaceOptionsQuestion',
data() {
return {
replaceOptions: this.isMultiSelect ? [] : ''
};
name: 'replace-options-question',
components: {
buttonQuestion
},
props: {
isAvailable: Boolean,
@ -41,18 +41,11 @@ export default {
cmsWidgetName: String,
isRequired: Boolean
},
methods: {
initializeComponent(replaceOptions) {
this.replaceOptions = replaceOptions;
},
updateSelectedValues() {
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
if (Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
this.selectedValues = this.isMultiSelect
? [this.answersToDisplay[0].Name]
: this.answersToDisplay[0].Name;
}
}
emits: ['update:modelValue'],
data() {
return {
replaceOptions: this.isMultiSelect ? [] : ''
};
},
computed: {
questionText() {
@ -62,10 +55,10 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
this.$emit('update:modelValue', newValue);
}
},
@ -74,8 +67,8 @@ export default {
? this.answersFromCms.filter((ans) => {
const name = ans.Name.split('-');
return this.filterByVehicleCategory
? name[0].toUpperCase() === this.mainStore.vehicle.category &&
this.replaceOptions.includes(name[1])
? name[0].toUpperCase() === this.mainStore.vehicle.category
&& this.replaceOptions.includes(name[1])
: this.replaceOptions.includes(ans.Name);
})
: [];
@ -93,6 +86,7 @@ export default {
watch: {
isAvailable(val) {
// CHECK TO UPDATE SELECTED VALUES WHEN ISAVAILABLE IS TRUE
// eslint-disable-next-line no-unused-expressions
val && this.updateSelectedValues();
},
shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) {
@ -101,8 +95,18 @@ export default {
}
}
},
components: {
buttonQuestion
methods: {
initializeComponent(replaceOptions) {
this.replaceOptions = replaceOptions;
},
updateSelectedValues() {
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
if (Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
this.selectedValues = this.isMultiSelect
? [this.answersToDisplay[0].Name]
: this.answersToDisplay[0].Name;
}
}
}
};
</script>

View file

@ -69,19 +69,17 @@ describe('replace-options-question.vue', () => {
driverSideReplaceOptions,
passengerSideReplaceOptions,
driverSideOptions,
passengerSideOptions,
passengerSideOptions
} = setupMocks({});
// Act
sideDoorOptions.methods.initializeComponent.call(
wrapper.vm,
sideDoorOptions.methods.initializeComponent.call(wrapper.vm,
cmsContent,
driverSideReplaceOptions,
passengerSideReplaceOptions,
driverSideOptions,
passengerSideOptions,
'car-group'
);
'car-group');
// Assert
expect(wrapper.vm.answersToDisplay).toStrictEqual([]);
@ -110,8 +108,8 @@ function setupMocks({
};
mountOptions.propsData = {
modelValue: modelValueProp,
groupName: groupName,
selectedDamageLocations: selectedDamageLocations,
groupName,
selectedDamageLocations
};
mountOptions.mixins = [mockMixin];
@ -122,7 +120,7 @@ function setupMocks({
OptionsWrapper[1].vm.initializeComponent = replaceOptionsQuestion.methods.initializeComponent;
// Mock CMS content
const cmsContent = {
groupName: groupName,
groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers
};

View file

@ -1,39 +1,41 @@
<template>
<div class="side-door-options">
<transition name="fade" mode="out-in">
<transition
name="fade"
mode="out-in">
<div
class="side-doors"
v-if="selectedDamageLocations.includes('SideDoor')"
class="side-doors"
aria-live="polite">
<buttonQuestion
v-model="selectedDoorSidesValues"
:questionText="questionText"
isMultiSelect
:answers="answersToDisplay"
:groupName="groupName"
buttonTypeString="listCard"
v-model="selectedDoorSidesValues"
validationRules="damage-side-required"
isRequired />
</div>
</transition>
<replaceOptionsQuestion
ref="driverSideOptions"
v-model="selectedDriverSideReplaceOptionsValues"
cmsWidgetName="DriverSideReplaceOptionsQuestion"
:isAvailable="isDriverSideReplaceOptionsQuestionAvailable"
groupName="driverSideOptions"
isMultiSelect
filterByVehicleCategory
v-model="selectedDriverSideReplaceOptionsValues"
validationRules="driver-side-options-required"
isRequired />
<replaceOptionsQuestion
ref="passengerSideOptions"
v-model="selectedPassengerSideReplaceOptionsValues"
cmsWidgetName="PassengerSideReplaceOptionsQuestion"
:isAvailable="isPassengerSideReplaceOptionsQuestionAvailable"
groupName="passengerSideOptions"
isMultiSelect
filterByVehicleCategory
v-model="selectedPassengerSideReplaceOptionsValues"
validationRules="passenger-side-options-required"
isRequired />
</div>
@ -53,30 +55,18 @@ defineRule('driver-side-options-required', required(errorMessages.DRIVER_SIDE_OP
defineRule('passenger-side-options-required', required(errorMessages.PASSENGER_SIDE_OPTIONS_REQUIRED));
export default {
name: 'sideDoorOptions',
name: 'side-door-options',
components: {
buttonQuestion,
replaceOptionsQuestion
},
props: {
groupName: String,
modelValue: Object,
selectedDamageLocations: Array,
cmsWidgetName: String
},
methods: {
initializeComponent(driverSideOptions, passengerSideOptions) {
this.$refs.driverSideOptions.initializeComponent(driverSideOptions);
this.$refs.passengerSideOptions.initializeComponent(passengerSideOptions);
},
getSideDoorReplacementOptions(
selectedDoorSides,
selectedDriverSideReplaceOptions,
selectedPassengerSideReplaceOptions
) {
return {
selectedDoorSides: selectedDoorSides,
selectedDriverSideReplaceOptions: selectedDriverSideReplaceOptions,
selectedPassengerSideReplaceOptions: selectedPassengerSideReplaceOptions
};
},
},
emits: ['update:modelValue'],
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
@ -85,47 +75,41 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
this.$emit('update:modelValue', newValue);
}
},
selectedDoorSidesValues: {
get: function () {
get() {
return this.selectedValues.selectedDoorSides;
},
set: function (newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(
newValue,
set(newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(newValue,
this.selectedValues.selectedDriverSideReplaceOptions,
this.selectedValues.selectedPassengerSideReplaceOptions
);
this.selectedValues.selectedPassengerSideReplaceOptions);
}
},
selectedDriverSideReplaceOptionsValues: {
get: function () {
get() {
return this.selectedValues.selectedDriverSideReplaceOptions;
},
set: function (newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(
this.selectedValues.selectedDoorSides,
set(newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(this.selectedValues.selectedDoorSides,
newValue,
this.selectedValues.selectedPassengerSideReplaceOptions
);
this.selectedValues.selectedPassengerSideReplaceOptions);
}
},
selectedPassengerSideReplaceOptionsValues: {
get: function () {
get() {
return this.selectedValues.selectedPassengerSideReplaceOptions;
},
set: function (newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(
this.selectedValues.selectedDoorSides,
set(newValue) {
this.selectedValues = this.getSideDoorReplacementOptions(this.selectedValues.selectedDoorSides,
this.selectedValues.selectedDriverSideReplaceOptions,
newValue
);
newValue);
}
},
answersToDisplay() {
@ -144,24 +128,35 @@ export default {
},
isDriverSideReplaceOptionsQuestionAvailable() {
return (
Array.isArray(this.selectedDoorSidesValues) &&
this.selectedDoorSidesValues.includes(damageLocationsSelected.DRIVERSIDE) &&
Array.isArray(this.selectedDamageLocations) &&
this.selectedDamageLocations.includes(damageLocationsSelected.SIDEDOOR)
Array.isArray(this.selectedDoorSidesValues)
&& this.selectedDoorSidesValues.includes(damageLocationsSelected.DRIVERSIDE)
&& Array.isArray(this.selectedDamageLocations)
&& this.selectedDamageLocations.includes(damageLocationsSelected.SIDEDOOR)
);
},
isPassengerSideReplaceOptionsQuestionAvailable() {
return (
Array.isArray(this.selectedDoorSidesValues) &&
this.selectedDoorSidesValues.includes(damageLocationsSelected.PASSENGERSIDE) &&
Array.isArray(this.selectedDamageLocations) &&
this.selectedDamageLocations.includes(damageLocationsSelected.SIDEDOOR)
Array.isArray(this.selectedDoorSidesValues)
&& this.selectedDoorSidesValues.includes(damageLocationsSelected.PASSENGERSIDE)
&& Array.isArray(this.selectedDamageLocations)
&& this.selectedDamageLocations.includes(damageLocationsSelected.SIDEDOOR)
);
}
},
components: {
buttonQuestion,
replaceOptionsQuestion
methods: {
initializeComponent(driverSideOptions, passengerSideOptions) {
this.$refs.driverSideOptions.initializeComponent(driverSideOptions);
this.$refs.passengerSideOptions.initializeComponent(passengerSideOptions);
},
getSideDoorReplacementOptions(selectedDoorSides,
selectedDriverSideReplaceOptions,
selectedPassengerSideReplaceOptions) {
return {
selectedDoorSides,
selectedDriverSideReplaceOptions,
selectedPassengerSideReplaceOptions
};
}
}
};
</script>

View file

@ -4,7 +4,7 @@ import { createTestingPinia } from '@pinia/testing';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
import { routerParams } from '@/router/router-params';
import { vehicleCategories } from '@/constants/vehicle-categories';
import VehicleDamageComponent from './vehicle-damage.vue';
import VehicleDamageComponent from '@/layouts/vehicle-damage/vehicle-damage';
const mockRoute = {
params: {}
@ -74,9 +74,6 @@ describe('vehicle-damage.vue', () => {
await flushPromises();
expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
expect(mockRouter.navigate)
.toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
mockRoute
);
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_REPAIR, mockRoute);
});
});

View file

@ -1,63 +1,67 @@
<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"/>
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<div class="container-fluid pb-2">
<div class="row mt-2 px-3">
<div class="col">
<alert
ref="vehicleChangeAlert"
v-if="shouldDisplayVehicleChangeAlert"
class="mt-5 mb-0"
cmsWidgetName="VehicleChangeAlert"
alertClass="alert-warning"
:isDismissible="false" />
v-if="shouldDisplayVehicleChangeAlert"
ref="vehicleChangeAlert"
class="mt-5 mb-0"
cmsWidgetName="VehicleChangeAlert"
alertClass="alert-warning"
:isDismissible="false" />
<vehicleBanner
class="mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
class="mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<damageLocationQuestion
ref="damageLocation"
cmsWidgetName="DamageLocationQuestion"
v-model="selectedDamageLocations"
groupName="DamageLocationQuestion" />
ref="damageLocation"
v-model="selectedDamageLocations"
cmsWidgetName="DamageLocationQuestion"
groupName="DamageLocationQuestion" />
<windshieldOptions
ref="windshieldOptions"
v-model="selectedWindshieldOptions"
:hasRepairReplaceConflict="hasRepairReplaceConflict"
:hasSplitSingleConflict="hasSplitSingleConflict"
:selectedDamageLocations="selectedDamageLocations"
class="mb-3" />
ref="windshieldOptions"
v-model="selectedWindshieldOptions"
:hasRepairReplaceConflict="hasRepairReplaceConflict"
:hasSplitSingleConflict="hasSplitSingleConflict"
:selectedDamageLocations="selectedDamageLocations"
class="mb-3" />
<alert
v-if="hasRepairReplaceConflict"
class="my-5"
cmsWidgetName="HasReplacementConflict"
alertClass="alert-danger"
:isDismissible="false" />
v-if="hasRepairReplaceConflict"
class="my-5"
cmsWidgetName="HasReplacementConflict"
alertClass="alert-danger"
:isDismissible="false" />
<sideDoorOptions
ref="sideDoorOptions"
cmsWidgetName="SideDoorSideQuestion"
groupName="SideDoorSideQuestion"
v-model="sideDoorOptionsData"
v-show="!hasRepairReplaceConflict"
:selectedDamageLocations="selectedDamageLocations" class="mb-1"/>
v-show="!hasRepairReplaceConflict"
ref="sideDoorOptions"
v-model="sideDoorOptionsData"
cmsWidgetName="SideDoorSideQuestion"
groupName="SideDoorSideQuestion"
:selectedDamageLocations="selectedDamageLocations"
class="mb-1" />
<replaceOptionsQuestion
ref="backGlassOptions"
cmsWidgetName="RearReplaceOptionsQuestion"
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
v-model="selectedRearReplaceOptions"
groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required" />
ref="backGlassOptions"
v-model="selectedRearReplaceOptions"
cmsWidgetName="RearReplaceOptionsQuestion"
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required" />
<site-footer
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction"
ref="siteFooter"
/>
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -94,6 +98,20 @@ defineRule('replace-options-required', required(errorMessages.REPLACE_OPTIONS_RE
export default {
name: 'vehicle-damage',
components: {
siteHeader,
siteFooter,
vehicleBanner,
siteSubHeader,
sideDoorOptions,
damageLocationQuestion,
windshieldOptions,
replaceOptionsQuestion,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
alert
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const store = useMainStore();
@ -120,16 +138,10 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.damageLocation.initializeComponent(resultMap.damageOptions);
vm.$refs.sideDoorOptions.initializeComponent(
resultMap.damageOptions.driverSideOptions.availableReplacementOptions,
resultMap.damageOptions.passengerSideOptions.availableReplacementOptions
);
vm.$refs.windshieldOptions.initializeComponent(
resultMap.damageOptions.windshieldOptions.availableReplacementOptions
);
vm.$refs.backGlassOptions.initializeComponent(
resultMap.damageOptions.backGlassOptions.availableReplacementOptions
);
vm.$refs.sideDoorOptions.initializeComponent(resultMap.damageOptions.driverSideOptions.availableReplacementOptions,
resultMap.damageOptions.passengerSideOptions.availableReplacementOptions);
vm.$refs.windshieldOptions.initializeComponent(resultMap.damageOptions.windshieldOptions.availableReplacementOptions);
vm.$refs.backGlassOptions.initializeComponent(resultMap.damageOptions.backGlassOptions.availableReplacementOptions);
});
},
setup() {
@ -149,6 +161,71 @@ export default {
selectedRearReplaceOptions: this.getRearReplaceOptionsFromStore()
};
},
computed: {
isWindshieldDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.WINDSHIELD);
},
isSideDoorDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.SIDEDOOR);
},
isRearWindowDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => selectedDamages.toUpperCase() === damageLocationsCms.REARWINDOW);
},
isWindshieldRepair() {
return (
this.isWindshieldDamageLocation
&& this.selectedWindshieldOptions.selectedWindshieldDamageType
=== damageLocationsSelected.REPAIR
);
},
isDriverSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this
.sideDoorOptionsData.selectedDoorSides
.some((selectedDriverSide) => selectedDriverSide.toUpperCase() === damageLocationsCms.DRIVERSIDE);
},
isPassengerSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this
.sideDoorOptionsData.selectedDoorSides
.some((selectedPassengerSide) => selectedPassengerSide.toUpperCase() === damageLocationsCms.PASSENGERSIDE);
},
hasRepairReplaceConflict() {
return (
this.isWindshieldDamageLocation
&& this.selectedDamageLocations.length > 1
&& this.isWindshieldRepair
);
},
hasSplitSingleConflict() {
if (
!this.selectedDamageLocations?.includes('Windshield')
|| this.selectedWindshieldOptions.selectedWindshieldDamageType
=== damageLocationsSelected.REPAIR
|| !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions
) return false;
return (
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedSingleWindshield) => (
selectedSingleWindshield.toUpperCase()
=== damageLocationsSelected.SINGLE.toUpperCase()
))
&& (this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedDriverWindshield) => (
selectedDriverWindshield.toUpperCase()
=== damageLocationsSelected.DRIVER.toUpperCase()
))
|| this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some((selectedPassengerWindshield) => (
selectedPassengerWindshield.toUpperCase()
=== damageLocationsSelected.PASSENGER.toUpperCase()
)))
);
},
shouldDisplayVehicleChangeAlert() {
return this.$route.params[this.routerParams.DISPLAY_VEHICLE_CHANGE_ALERT];
}
},
methods: {
arePagePrerequisitesValid() {
if (this.mainStore.order.vehicle.carId) {
@ -163,31 +240,25 @@ export default {
},
getDamageLocationsFromStore() {
var glassSelections = [];
const glassSelections = [];
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return glass.glassLocation === damageLocationsSelected.WINDSHIELD;
}) ||
this.mainStore.order.damage.isRepair
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.WINDSHIELD)
|| this.mainStore.order.damage.isRepair
) {
glassSelections.push(damageLocationsSelected.WINDSHIELD);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return (
glass.glassLocation === damageLocationsSelected.DRIVER ||
glass.glassLocation === damageLocationsSelected.PASSENGER
);
})
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.DRIVER
|| glass.glassLocation === damageLocationsSelected.PASSENGER
))
) {
glassSelections.push(damageLocationsSelected.SIDEDOOR);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return glass.glassLocation === damageLocationsSelected.REAR;
})
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.REAR)
) {
glassSelections.push(damageLocationsSelected.REARWINDOW);
}
@ -196,7 +267,7 @@ export default {
},
getWindshieldOptionsFromStore() {
var windShieldOptions = {
const windShieldOptions = {
selectedWindshieldDamageType: '',
selectedWindshieldChipCount: null,
selectedWindshieldReplaceOptions: []
@ -209,48 +280,36 @@ export default {
windShieldOptions.selectedWindshieldChipCount = this.mainStore.order.damage.numberOfChips;
} else {
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return (
glass.glassLocation === damageLocationsSelected.WINDSHIELD &&
glass.glassName === damageLocationsSelected.SINGLE
);
})
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.SINGLE
))
) {
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.SINGLE
);
windShieldOptions.selectedWindshieldDamageType
= damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.SINGLE);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return (
glass.glassLocation === damageLocationsSelected.WINDSHIELD &&
glass.glassName === damageLocationsSelected.DRIVER
);
})
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.DRIVER
))
) {
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.DRIVER
);
windShieldOptions.selectedWindshieldDamageType
= damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.DRIVER);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return (
glass.glassLocation === damageLocationsSelected.WINDSHIELD &&
glass.glassName === damageLocationsSelected.PASSENGER
);
})
this.mainStore.order.damage.glassToReplace?.some((glass) => (
glass.glassLocation === damageLocationsSelected.WINDSHIELD
&& glass.glassName === damageLocationsSelected.PASSENGER
))
) {
windShieldOptions.selectedWindshieldDamageType =
damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(
damageLocationsSelected.PASSENGER
);
windShieldOptions.selectedWindshieldDamageType
= damageLocationsSelected.REPLACE;
windShieldOptions.selectedWindshieldReplaceOptions.push(damageLocationsSelected.PASSENGER);
}
}
@ -258,19 +317,15 @@ export default {
},
getDoorSidesFromStore() {
var doorSides = [];
const doorSides = [];
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return glass.glassLocation === damageLocationsSelected.DRIVER;
})
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.DRIVER)
) {
doorSides.push(damageLocationsSelected.DRIVERSIDE);
}
if (
this.mainStore.order.damage.glassToReplace?.some((glass) => {
return glass.glassLocation === damageLocationsSelected.PASSENGER;
})
this.mainStore.order.damage.glassToReplace?.some((glass) => glass.glassLocation === damageLocationsSelected.PASSENGER)
) {
doorSides.push(damageLocationsSelected.PASSENGERSIDE);
}
@ -279,7 +334,7 @@ export default {
},
getDriverSideReplaceOptionsFromStore() {
var driverSideReplaceOptions = [];
const driverSideReplaceOptions = [];
this.mainStore.order.damage.glassToReplace?.forEach((glass) => {
if (glass.glassLocation === damageLocationsSelected.DRIVER) {
@ -291,7 +346,7 @@ export default {
},
getPassengerSideReplaceOptionsFromStore() {
var passengerSideReplaceOptions = [];
const passengerSideReplaceOptions = [];
this.mainStore.order.damage.glassToReplace?.forEach((glass) => {
if (glass.glassLocation === damageLocationsSelected.PASSENGER) {
@ -303,9 +358,8 @@ export default {
},
getRearReplaceOptionsFromStore() {
var rearReplaceOptions = this.mainStore.order.damage.glassToReplace?.filter(
(glass) => glass.glassLocation === damageLocationsSelected.REAR
)[0]?.glassName;
const rearReplaceOptions = this.mainStore.order.damage
.glassToReplace?.filter((glass) => glass.glassLocation === damageLocationsSelected.REAR)[0]?.glassName;
return rearReplaceOptions;
},
@ -319,37 +373,27 @@ export default {
navigateForward() {
if (this.mainStore.damage.isRepair) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
this.$route
);
} else {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
this.$route);
} else if (this.mainStore.order.vehicle.vin) {
// If vin already exists, navigate directly to vin-lookup
if (this.mainStore.order.vehicle.vin) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_VIN,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
this.$route
);
}
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN,
this.$route);
} else {
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITHOUT_VIN,
this.$route);
}
},
selectedGlassToReplace() {
const selectedGlassToReplace = [];
if (this.isWindshieldDamageLocation && !this.isWindshieldRepair) {
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach(
(wsItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.WINDSHIELD,
glassName: wsItem
});
}
);
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.forEach((wsItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.WINDSHIELD,
glassName: wsItem
});
});
}
if (this.isDriverSideReplace) {
@ -362,14 +406,12 @@ export default {
}
if (this.isPassengerSideReplace) {
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach(
(passengerItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.PASSENGER,
glassName: passengerItem
});
}
);
this.sideDoorOptionsData.selectedPassengerSideReplaceOptions.forEach((passengerItem) => {
selectedGlassToReplace.push({
glassLocation: damageLocationsSelected.PASSENGER,
glassName: passengerItem
});
});
}
if (this.isRearWindowDamageLocation) {
@ -381,103 +423,6 @@ export default {
return selectedGlassToReplace;
}
},
computed: {
isWindshieldDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => {
return selectedDamages.toUpperCase() === damageLocationsCms.WINDSHIELD;
});
},
isSideDoorDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => {
return selectedDamages.toUpperCase() === damageLocationsCms.SIDEDOOR;
});
},
isRearWindowDamageLocation() {
return this.selectedDamageLocations.some((selectedDamages) => {
return selectedDamages.toUpperCase() === damageLocationsCms.REARWINDOW;
});
},
isWindshieldRepair() {
return (
this.isWindshieldDamageLocation &&
this.selectedWindshieldOptions.selectedWindshieldDamageType ===
damageLocationsSelected.REPAIR
);
},
isDriverSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this.sideDoorOptionsData.selectedDoorSides.some((selectedDriverSide) => {
return selectedDriverSide.toUpperCase() === damageLocationsCms.DRIVERSIDE;
});
},
isPassengerSideReplace() {
if (!this.isSideDoorDamageLocation) return false;
return this.sideDoorOptionsData.selectedDoorSides.some((selectedPassengerSide) => {
return selectedPassengerSide.toUpperCase() === damageLocationsCms.PASSENGERSIDE;
});
},
hasRepairReplaceConflict() {
return (
this.isWindshieldDamageLocation &&
this.selectedDamageLocations.length > 1 &&
this.isWindshieldRepair
);
},
hasSplitSingleConflict() {
if (
!this.selectedDamageLocations?.includes('Windshield') ||
this.selectedWindshieldOptions.selectedWindshieldDamageType ===
damageLocationsSelected.REPAIR ||
!this.selectedWindshieldOptions.selectedWindshieldReplaceOptions
)
return false;
return (
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedSingleWindshield) => {
return (
selectedSingleWindshield.toUpperCase() ===
damageLocationsSelected.SINGLE.toUpperCase()
);
}
) &&
(this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedDriverWindshield) => {
return (
selectedDriverWindshield.toUpperCase() ===
damageLocationsSelected.DRIVER.toUpperCase()
);
}
) ||
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(
(selectedPassengerWindshield) => {
return (
selectedPassengerWindshield.toUpperCase() ===
damageLocationsSelected.PASSENGER.toUpperCase()
);
}
))
);
},
shouldDisplayVehicleChangeAlert() {
return this.$route.params[this.routerParams.DISPLAY_VEHICLE_CHANGE_ALERT];
}
},
components: {
siteHeader,
siteFooter,
vehicleBanner,
siteSubHeader,
sideDoorOptions,
damageLocationQuestion,
windshieldOptions,
replaceOptionsQuestion,
Form,
alert
}
};
</script>

View file

@ -36,7 +36,7 @@ function setupMocks({
// Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn(),
getCmsContent: jest.fn()
}
};
mountOptions.propsData = {
@ -48,7 +48,7 @@ function setupMocks({
// Mock CMS content
const cmsContent = {
groupName: groupName,
groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers
};

View file

@ -1,13 +1,18 @@
<template>
<transition name="fade" mode="out-in">
<div class="windshield-chip-count-question" v-if="isAvailable" aria-live="polite">
<transition
name="fade"
mode="out-in">
<div
v-if="isAvailable"
class="windshield-chip-count-question"
aria-live="polite">
<buttonQuestion
v-model="selectedValue"
:questionText="questionText"
:answers="answersFromCms"
:groupName="groupName"
buttonTypeString="listButtonHorizontal"
useTextForValue
v-model="selectedValue"
:validationRules="validationRules"
isRequired />
</div>
@ -18,7 +23,10 @@
import buttonQuestion from '@/digital-components/button-question/button-question';
export default {
name: 'windshieldOptions',
name: 'windshield-options',
components: {
buttonQuestion
},
props: {
modelValue: [String, Number],
groupName: String,
@ -26,6 +34,7 @@ export default {
validationRules: String,
cmsWidgetName: String
},
emits: ['update:modelValue'],
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
@ -34,17 +43,14 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValue: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
const numberValue = Number(newValue);
this.$emit('update:modelValue', numberValue);
}
}
},
components: {
buttonQuestion
}
};
</script>

View file

@ -1,12 +1,17 @@
<template>
<transition name="fade" mode="out-in">
<div class="windshield-damage-type-question" v-if="isAvailable" aria-live="polite">
<transition
name="fade"
mode="out-in">
<div
v-if="isAvailable"
class="windshield-damage-type-question"
aria-live="polite">
<buttonQuestion
v-model="selectedValues"
:questionText="questionText"
:answers="answersFromCms"
:groupName="groupName"
buttonTypeString="listCard"
v-model="selectedValues"
:suppressError="suppressError"
:validationRules="validationRules"
isRequired />
@ -18,7 +23,10 @@
import buttonQuestion from '@/digital-components/button-question/button-question';
export default {
name: 'windshieldDamageTypeQuestion',
name: 'windshield-damage-type-question',
components: {
buttonQuestion
},
props: {
modelValue: String,
groupName: String,
@ -27,6 +35,7 @@ export default {
validationRules: String,
cmsWidgetName: String
},
emits: ['update:modelValue'],
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
@ -35,16 +44,13 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
this.$emit('update:modelValue', newValue);
}
}
},
components: {
buttonQuestion
}
};
</script>

View file

@ -1,11 +1,11 @@
<template>
<div class="windshield-options">
<windshieldDamageTypeQuestion
v-model="selectedWindshieldDamageTypeValue"
cmsWidgetName="WindshieldDamageTypeQuestion"
:isAvailable="isWindshieldDamageLocation"
:suppressError="hasRepairReplaceConflict || showNoReplacementAvailableError"
groupName="WindshieldDamageTypeQuestion"
v-model="selectedWindshieldDamageTypeValue"
:validationRules="windshieldDamageTypeQuestionValidationRules" />
<alert
v-if="showNoReplacementAvailableError"
@ -14,18 +14,18 @@
alertClass="alert-danger"
:isDismissible="false" />
<windshieldChipCountQuestion
v-model="selectedWindshieldChipCountValues"
cmsWidgetName="WindshieldChipCountQuestion"
:isAvailable="isRepairOptionSelected && !hasRepairReplaceConflict"
groupName="WindshieldChipCountQuestion"
v-model="selectedWindshieldChipCountValues"
validationRules="windshield-chip-count-required" />
<replaceOptionsQuestion
ref="replaceOptionsQuestion"
v-model="selectedWindshieldReplaceOptionsValues"
cmsWidgetName="WindshieldReplaceOptionsQuestion"
:isAvailable="isReplaceOptionSelected"
isMultiSelect
groupName="WindshieldReplaceOptions"
v-model="selectedWindshieldReplaceOptionsValues"
validationRules="windshield-replace-options-required|prevent-split-and-single-together"
:suppressError="hasSplitSingleConflict"
isRequired />
@ -39,8 +39,10 @@
</template>
<script>
import windshieldDamageTypeQuestion from '@/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question';
import windshieldChipCountQuestion from '@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question';
import windshieldDamageTypeQuestion from
'@/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question';
import windshieldChipCountQuestion from
'@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question';
import replaceOptionsQuestion from '@/layouts/vehicle-damage/replace-options-question/replace-options-question';
import alert from '@/ux-components/alert/alert';
@ -50,38 +52,26 @@ import { errorMessages } from '@/constants/error-messages';
import { damageLocationsSelected } from '@/constants/damage-locations-selected.js';
// DEFINE VALIDATION RULES
defineRule(
'windshield-damage-type-required',
required(errorMessages.WINDSHIELD_DAMAGE_TYPE_REQUIRED)
);
defineRule(
'windshield-chip-count-required',
required(errorMessages.WINDSHIELD_CHIP_COUNT_REQUIRED)
);
defineRule(
'windshield-replace-options-required',
required(errorMessages.WINSHIELD_REPLACE_OPTIONS_REQUIRED)
);
defineRule('windshield-damage-type-required',
required(errorMessages.WINDSHIELD_DAMAGE_TYPE_REQUIRED));
defineRule('windshield-chip-count-required',
required(errorMessages.WINDSHIELD_CHIP_COUNT_REQUIRED));
defineRule('windshield-replace-options-required',
required(errorMessages.WINDSHIELD_REPLACE_OPTIONS_REQUIRED));
defineRule(
'check-for-repair-and-replace',
(selectedWindshieldDamageType, selectedDamageLocations) => {
return (
selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR ||
(!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) &&
!selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD)) ||
selectedDamageLocations[0].length === 1
);
}
);
defineRule('repair-only', (value) => {
return value.toString() === damageLocationsSelected.REPAIR;
});
defineRule('check-for-repair-and-replace',
(selectedWindshieldDamageType, selectedDamageLocations) => (
selectedWindshieldDamageType.toString() !== damageLocationsSelected.REPAIR
|| (!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD)
&& !selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD))
|| selectedDamageLocations[0].length === 1
));
defineRule('repair-only', (value) => value.toString() === damageLocationsSelected.REPAIR);
defineRule('prevent-split-and-single-together', (value) => {
if (
value.toString().toUpperCase().includes(damageLocationsSelected.SINGLE.toUpperCase()) &&
(value.toString().toUpperCase().includes(damageLocationsSelected.DRIVER.toUpperCase()) ||
value
value.toString().toUpperCase().includes(damageLocationsSelected.SINGLE.toUpperCase())
&& (value.toString().toUpperCase().includes(damageLocationsSelected.DRIVER.toUpperCase())
|| value
.toString()
.toUpperCase()
.includes(damageLocationsSelected.PASSENGER.toUpperCase()))
@ -92,12 +82,12 @@ defineRule('prevent-split-and-single-together', (value) => {
});
export default {
name: 'windshieldOptions',
data() {
return {
windshieldAvailableReplacementOptions: Object
};
name: 'windshield-options',
components: {
windshieldDamageTypeQuestion,
windshieldChipCountQuestion,
replaceOptionsQuestion,
alert
},
props: {
@ -106,102 +96,75 @@ export default {
hasRepairReplaceConflict: Boolean,
hasSplitSingleConflict: Boolean
},
methods: {
initializeComponent(windshieldAvailableReplacementOptions) {
this.windshieldAvailableReplacementOptions = windshieldAvailableReplacementOptions;
this.$refs.replaceOptionsQuestion.initializeComponent(
windshieldAvailableReplacementOptions
);
},
getWindshieldOptions(
selectedWindshieldDamageType,
selectedWindshieldChipCount,
selectedWindshieldReplaceOptions
) {
// ONLY UPDATE THE NEW VALUE IF IT IS TRUTHY (NOT NULL)
return {
selectedWindshieldDamageType: selectedWindshieldDamageType
? selectedWindshieldDamageType
: this.selectedValues.selectedWindshieldDamageType,
selectedWindshieldChipCount: selectedWindshieldChipCount
? selectedWindshieldChipCount
: this.selectedValues.selectedWindshieldChipCount,
selectedWindshieldReplaceOptions: selectedWindshieldReplaceOptions
? selectedWindshieldReplaceOptions
: this.selectedValues.selectedWindshieldReplaceOptions,
};
}
emits: ['update:modelValue'],
data() {
return {
windshieldAvailableReplacementOptions: Object
};
},
computed: {
selectedValues: {
get: function () {
get() {
return this.modelValue;
},
set: function (newValue) {
set(newValue) {
this.$emit('update:modelValue', newValue);
}
},
selectedWindshieldDamageTypeValue: {
get: function () {
get() {
return this.selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD)
? this.selectedValues.selectedWindshieldDamageType
: null;
},
set: function (newValue) {
set(newValue) {
this.selectedValues = this.getWindshieldOptions(newValue, null, null);
},
}
},
selectedWindshieldChipCountValues: {
get: function () {
get() {
return this.selectedValues.selectedWindshieldChipCount;
},
set: function (newValue) {
this.selectedValues = this.getWindshieldOptions(
this.selectedWindshieldDamageTypeValue,
set(newValue) {
this.selectedValues = this.getWindshieldOptions(this.selectedWindshieldDamageTypeValue,
newValue,
null
);
null);
}
},
selectedWindshieldReplaceOptionsValues: {
get: function () {
get() {
return this.selectedValues.selectedWindshieldReplaceOptions;
},
set: function (newValue) {
this.selectedValues = this.getWindshieldOptions(
this.selectedWindshieldDamageTypeValue,
set(newValue) {
this.selectedValues = this.getWindshieldOptions(this.selectedWindshieldDamageTypeValue,
null,
newValue
);
newValue);
}
},
isWindshieldDamageLocation() {
return this.selectedDamageLocations.some(
(selectedDamageLocation) =>
selectedDamageLocation === damageLocationsSelected.WINDSHIELD
);
return this.selectedDamageLocations.some((selectedDamageLocation) =>
selectedDamageLocation === damageLocationsSelected.WINDSHIELD);
},
isRepairOptionSelected() {
if (!this.selectedWindshieldDamageTypeValue) return false;
return (
this.selectedWindshieldDamageTypeValue === damageLocationsSelected.REPAIR &&
this.isWindshieldDamageLocation
this.selectedWindshieldDamageTypeValue === damageLocationsSelected.REPAIR
&& this.isWindshieldDamageLocation
);
},
isReplaceOptionSelected() {
if (!this.selectedWindshieldDamageTypeValue) return false;
return (
this.selectedWindshieldDamageTypeValue === damageLocationsSelected.REPLACE &&
this.isWindshieldDamageLocation
this.selectedWindshieldDamageTypeValue === damageLocationsSelected.REPLACE
&& this.isWindshieldDamageLocation
);
},
isWindshieldReplaceAvailable() {
return !(
Array.isArray(this.windshieldAvailableReplacementOptions) &&
this.windshieldAvailableReplacementOptions.length < 1
Array.isArray(this.windshieldAvailableReplacementOptions)
&& this.windshieldAvailableReplacementOptions.length < 1
);
},
isSplitWindshieldOption() {
@ -219,8 +182,8 @@ export default {
},
windshieldDamageTypeQuestionValidationRules() {
// Note: the validation rules string is not dynamic (it cannot be changed once component has been created)
let validationRules =
'windshield-damage-type-required|check-for-repair-and-replace:@DamageLocationQuestion';
let validationRules
= 'windshield-damage-type-required|check-for-repair-and-replace:@DamageLocationQuestion';
// if vehicle has no windshield replacement option
if (!this.isWindshieldReplaceAvailable) {
validationRules = validationRules.concat('|repair-only');
@ -228,11 +191,22 @@ export default {
return validationRules;
}
},
components: {
windshieldDamageTypeQuestion,
windshieldChipCountQuestion,
replaceOptionsQuestion,
alert
methods: {
initializeComponent(windshieldAvailableReplacementOptions) {
this.windshieldAvailableReplacementOptions = windshieldAvailableReplacementOptions;
this.$refs.replaceOptionsQuestion.initializeComponent(windshieldAvailableReplacementOptions);
},
getWindshieldOptions(selectedWindshieldDamageType,
selectedWindshieldChipCount,
selectedWindshieldReplaceOptions) {
// ONLY UPDATE THE NEW VALUE IF IT IS TRUTHY (NOT NULL)
return {
selectedWindshieldDamageType: selectedWindshieldDamageType || this.selectedValues.selectedWindshieldDamageType,
selectedWindshieldChipCount: selectedWindshieldChipCount || this.selectedValues.selectedWindshieldChipCount,
selectedWindshieldReplaceOptions: selectedWindshieldReplaceOptions || this.selectedValues.selectedWindshieldReplaceOptions
};
}
}
};
</script>

View file

@ -3,8 +3,7 @@ import { mount } from '@vue/test-utils';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
import { queryStrings } from '@/constants/query-strings';
import { GaActions } from '@/constants/analytics';
import VehicleLookup from './vehicle-lookup.vue';
import {vinLookupMethodSelections} from '@/constants/vin-lookup-methods';
import { vinLookupMethodSelections } from '@/constants/vin-lookup-methods';
import { useMainStore } from '@/store';
import { createTestingPinia } from '@pinia/testing';
import { mapStores } from 'pinia';
@ -12,6 +11,7 @@ import { defineRule } from 'vee-validate';
import { errorMessages } from '@/constants/error-messages';
import globalRules from '@/constants/global-rules';
import { required } from '@/helpers/validation-rules';
import VehicleLookup from '@/layouts/vehicle-lookup/vehicle-lookup';
const pinia = createTestingPinia();
useMainStore(pinia);
@ -59,7 +59,7 @@ function setupMocks() {
navigate: jest.fn()
};
const mockIsVinbyAddressPermissibleToBeFalse={
const mockIsVinbyAddressPermissibleToBeFalse = {
IsVinbyAddressPermissible: false
};
@ -126,7 +126,7 @@ function setupMocks() {
describe('vehicle-lookup.vue', () => {
test('IsVinbyAddressPermissible to be false', () => {
const { wrapper,mockIsVinbyAddressPermissibleToBeFalse } = setupMocks();
const { wrapper, mockIsVinbyAddressPermissibleToBeFalse } = setupMocks();
const vinLookupMethodsWrapper = wrapper.findComponent({ name: 'vin-lookup-methods' });
// Not using initializeComponent() because it doesn't trigger reactivity in test.
@ -139,7 +139,8 @@ describe('vehicle-lookup.vue', () => {
answersFromCms: Answers
});
const lookupByAddressOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-HomeAddress"]');
const lookupByAddressOptionLabel = wrapper
.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-HomeAddress"]');
expect(lookupByAddressOptionLabel.exists()).toBe(false);
});
@ -165,7 +166,8 @@ describe('vehicle-lookup.vue', () => {
answersFromCms: vinLookupMethodsMockData.Answers
});
const lookupByManualVinOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]');
const lookupByManualVinOptionLabel = wrapper
.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]');
await lookupByManualVinOptionLabel.trigger('mousedown');
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');
@ -193,7 +195,8 @@ describe('vehicle-lookup.vue', () => {
useMainStore().updateVehicleVin = jest.fn();
const lookupByManualVinOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]');
const lookupByManualVinOptionLabel = wrapper
.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-ManualVin"]');
await lookupByManualVinOptionLabel.trigger('mousedown');
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');
@ -218,7 +221,8 @@ describe('vehicle-lookup.vue', () => {
answersFromCms: vinLookupMethodsMockData.Answers
});
const lookupByLicensePlateOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-LicensePlate"]');
const lookupByLicensePlateOptionLabel = wrapper
.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-LicensePlate"]');
await lookupByLicensePlateOptionLabel.trigger('mousedown');
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');
@ -243,7 +247,8 @@ describe('vehicle-lookup.vue', () => {
answersFromCms: vinLookupMethodsMockData.Answers
});
const lookupByAddressOptionLabel = wrapper.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-HomeAddress"]');
const lookupByAddressOptionLabel = wrapper
.find('[data-test-id="vin-lookup-methods-button-question"] label[for="VinLookupMethods-HomeAddress"]');
await lookupByAddressOptionLabel.trigger('mousedown');
const continueButton = wrapper.get('[data-test-id="site-footer-main-button"]');

View file

@ -1,5 +1,7 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit">
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit">
<div class="page-container-grouped-styles">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
@ -8,10 +10,24 @@
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<VehicleBanner class="mt-2 mb-4" cms-widget-name="VehicleBannerWidget" :display-generic-vehicle-image="false" />
<SiteSubHeader cms-widget-name="SiteSubHeaderWidget" class="mt-5 mb-2" />
<VinLookupMethods v-model="selectedVinLookupMethod" cms-widget-name="VINLookupMethod" group-name="VinLookupMethods" ref="VinLookupMethods" />
<SiteFooter cms-widget-name="SiteFooterWidget" :is-forward-action-disabled="isForwardActionDisabled" @back-clicked="backButtonAction" @forward-clicked="forwardButtonAction" class="mt-5"/>
<VehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<SiteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-5 mb-2" />
<VinLookupMethods
ref="VinLookupMethods"
v-model="selectedVinLookupMethod"
cmsWidgetName="VINLookupMethod"
groupName="VinLookupMethods" />
<SiteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="isForwardActionDisabled"
class="mt-5"
@back-clicked="backButtonAction"
@forward-clicked="forwardButtonAction" />
</div>
</div>
</div>
@ -27,20 +43,20 @@ import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { settleAllPromises } from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin';
import {vinLookupMethodSelections} from '@/constants/vin-lookup-methods';
import { vinLookupMethodSelections } from '@/constants/vin-lookup-methods';
import { useMainStore } from '@/store';
// Import Component
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 VehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import VinLookupMethods from './vin-lookup-methods/vin-lookup-methods.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 VehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner';
import VinLookupMethods from '@/layouts/vehicle-lookup/vin-lookup-methods/vin-lookup-methods';
export default {
name: 'vehicle-lookup',
mixins: [BaseFormMixin],
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
SiteFooter,
SiteHeader,
@ -48,11 +64,7 @@ export default {
VehicleBanner,
VinLookupMethods
},
data() {
return {
selectedVinLookupMethod: null
};
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -71,6 +83,11 @@ export default {
vm.$refs.VinLookupMethods.initializeComponent();
});
},
data() {
return {
selectedVinLookupMethod: null
};
},
computed: {
isForwardActionDisabled() {
return this.selectedVinLookupMethod === null;

View file

@ -2,12 +2,11 @@
<ButtonQuestion
v-model="selectedValue"
:answers="answersFromCms"
button-type-string="listButton"
:question-text="questionTextFromCms"
is-required
:validation-rules="rules.optionRequired"
data-test-id="vin-lookup-methods-button-question"
/>
buttonTypeString="listButton"
:questionText="questionTextFromCms"
isRequired
:validationRules="rules.optionRequired"
data-test-id="vin-lookup-methods-button-question" />
</template>
<script>
@ -19,18 +18,12 @@ import { settleAllPromises } from '@/helpers/layout-helper';
import globalRules from '@/constants/global-rules';
// Import Component
import ButtonQuestion from '@/digital-components/button-question/button-question.vue';
import ButtonQuestion from '@/digital-components/button-question/button-question';
export default {
name: 'vin-lookup-methods',
data() {
return {
questionTextFromCms: '',
answersFromCms: [],
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
components: {
ButtonQuestion
},
props: {
modelValue: String,
@ -40,8 +33,14 @@ export default {
}
},
emits: ['update:modelValue'],
components: {
ButtonQuestion
data() {
return {
questionTextFromCms: '',
answersFromCms: [],
rules: {
optionRequired: globalRules.OPTION_REQUIRED
}
};
},
computed: {
selectedValue: {

View file

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import glassPartQuestion from '@/layouts/vehicle-parts/glass-part-question/glass-part-question.vue';
import glassPartQuestion from '@/layouts/vehicle-parts/glass-part-question/glass-part-question';
import { useMainStore } from '@/store';
const featureListData = {
@ -31,16 +31,12 @@ describe('glass-part-question.vue', () => {
// Act
await wrapper.vm.$nextTick();
console.log(wrapper.vm.featureListData['Green Tint'][0].Text)
console.log(wrapper.vm.featureListData['Green Tint'][0].Text);
// Assert
expect(Object.keys(wrapper.vm.featureListData).length).toBe(2);
expect(wrapper.vm.featureListData['Green Tint'][0].Text).toBe(
'heated glass, solar, 1 hole'
);
expect(wrapper.vm.featureListData['Green Tint'][0].Text).toBe('heated glass, solar, 1 hole');
expect(wrapper.vm.featureListData['Green Tint'][0].Name).toBe('DB12209GTYN');
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Text).toBe(
'heated glass, solar, 1 hole'
);
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Text).toBe('heated glass, solar, 1 hole');
expect(wrapper.vm.featureListData['Gray Tint Privacy'][0].Name).toBe('DB12209YPYN');
});
@ -161,8 +157,7 @@ describe('glass-part-question.vue', () => {
['Driver', 'Quarter', 'Green Tint', []]
];
test.each(partsForSelectedTintTestCases)(
'partsForSelectedTint returns correct parts',
test.each(partsForSelectedTintTestCases)('partsForSelectedTint returns correct parts',
async (glassLocation, glassName, selectedTint, expectedResults) => {
// Arrange
const pageData = { partsOrQuestions: [
@ -187,7 +182,7 @@ describe('glass-part-question.vue', () => {
{ partNumber: 'Windshield2', color: 'Green Tint' }
]
}
] }
] };
const { wrapper } = setupMocks({
glassLocationProp: glassLocation,
glassNameProp: glassName,
@ -196,12 +191,11 @@ describe('glass-part-question.vue', () => {
});
// Act
await wrapper.setData({ selectedTint: selectedTint });
await wrapper.setData({ selectedTint });
// Assert
expect(expectedResults).toEqual(wrapper.vm.partsForSelectedTint);
}
);
});
});
function setupMocks({ glassNameProp, glassLocationProp, colorAnswersProp, modelValueProp, pageData }) {

View file

@ -1,6 +1,8 @@
<template>
<div class="row">
<p class="mb-0 color-question-text">{{ colorQuestionText }}</p>
<p class="mb-0 color-question-text">
{{ colorQuestionText }}
</p>
</div>
<div class="nested-radio">
<div class="row my-2">
@ -14,7 +16,9 @@
isRequired
:groupName="replaceAllSpaceWithDash(`${glassLocation}-${glassName}`)"
:validationRules="tintValidationRules">
<div class="row my-2" aria-live="polite">
<div
class="row my-2"
aria-live="polite">
<div class="col">
<buttonQuestion
id="glass-part-question"
@ -50,15 +54,10 @@ import { errorMessages } from '@/constants/error-messages';
export default {
name: 'glass-part-question',
emits: ['update:modelValue'],
inheritAttrs: false,
data() {
return {
glassColorQuestion: '',
glassFeatureQuestion: '',
selectedTint: ''
};
components: {
buttonQuestion
},
inheritAttrs: false,
props: {
glassName: String,
glassLocation: String,
@ -69,11 +68,13 @@ export default {
default: () => []
}
},
mounted() {
this.LoadPreselectedValues();
},
components: {
buttonQuestion
emits: ['update:modelValue'],
data() {
return {
glassColorQuestion: '',
glassFeatureQuestion: '',
selectedTint: ''
};
},
computed: {
tintValidationRules() {
@ -90,23 +91,19 @@ export default {
return validationRuleName;
},
colorQuestionText() {
return getCustomTransformValue(
this.glassColorQuestion,
`${this.glassLocation} ${this.glassName}`
);
return getCustomTransformValue(this.glassColorQuestion,
`${this.glassLocation} ${this.glassName}`);
},
tintSelectionOptions() {
let tintOptions = [];
const tintOptions = [];
Object.keys(this.featureListData).forEach((tintOption) => {
tintOptions.push({
value: tintOption,
buttonLabel: tintOption,
buttonImage: require(`@/assets/img/tints/${this.getTintSourceImage(
this.glassLocation,
tintOption
)}`),
buttonImage: require(`@/assets/img/tints/${this.getTintSourceImage(this.glassLocation,
tintOption)}`)
});
});
@ -118,19 +115,18 @@ export default {
return this.modelValue?.partNumber;
},
set(newValue) {
const part = this.partsForSelectedTint.filter((part) => part.partNumber == newValue?.value)[0];
const part = this.partsForSelectedTint
.filter((partItem) => partItem.partNumber === newValue?.value)[0];
this.$emit('update:modelValue', part);
}
},
partsForSelectedTint() {
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(
(dataForGlassLocationAndName) =>
dataForGlassLocationAndName.glassName == this.glassName &&
dataForGlassLocationAndName.glassLocation == this.glassLocation
);
const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
return matchingGlassParts.filter((part) => part.color == this.selectedTint) ?? [];
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter((dataForGlassLocationAndName) =>
dataForGlassLocationAndName.glassName === this.glassName
&& dataForGlassLocationAndName.glassLocation === this.glassLocation);
const matchingGlassParts = matchingGlass?.length === 1 ? matchingGlass[0].parts : [];
return matchingGlassParts.filter((part) => part.color === this.selectedTint) ?? [];
},
// Creates a map of the feature list data in the correct Name/Value
@ -142,9 +138,9 @@ export default {
arr[itemColor] = arr[itemColor] || [];
// Map the data to Name/Text object for ButtonQuestion
const mappedItem = item.FeatureAnswers.reduce((featureArr, item) => {
featureArr['Text'] = item.FeatureAnswerText; // Display to User
featureArr['Name'] = item.PartNumber; // Backing Value
const mappedItem = item.FeatureAnswers.reduce((featureArr, it) => {
featureArr.Text = it.FeatureAnswerText; // Display to User
featureArr.Name = it.PartNumber; // Backing Value
return featureArr;
}, {});
@ -161,6 +157,14 @@ export default {
return this.mainStore.pageData(this.$route.query.issPage) ?? {};
}
},
watch: {
selectedTint() {
this.AutoSelectIfSinglePart();
}
},
mounted() {
this.LoadPreselectedValues();
},
methods: {
// Initialize the component data
initializeComponent(cmsContent) {
@ -173,7 +177,7 @@ export default {
getTintSourceImage(glassLocation, tintColor) {
const tintSourceObject = getTintImage(glassLocation, tintColor);
if (tintSourceObject === undefined || tintSourceObject.src == undefined) {
if (tintSourceObject === undefined || !tintSourceObject.src) {
return '';
}
@ -183,12 +187,12 @@ export default {
AutoSelectIfSinglePart() {
if (this.partsForSelectedTint?.length > 0) {
// Check if only a single part is present for the tint and set the v-model if it is.
if (this.partsForSelectedTint?.length == 1) {
if (this.partsForSelectedTint?.length === 1) {
this.selectedPartNumber = { value: this.partsForSelectedTint[0].partNumber };
// //select element with matching partNumber
this.$nextTick(() => {
const radioInput = document.querySelector('input[value=' + this.selectedPartNumber + ']');
const radioInput = document.querySelector(`input[value=${this.selectedPartNumber}]`);
// Fire a click event on the input so the field is updated
radioInput?.click();
});
@ -211,16 +215,11 @@ export default {
replaceAllSpaceWithDash(str) {
return String(str).replaceAll(' ', '-');
}
},
watch: {
selectedTint() {
this.AutoSelectIfSinglePart();
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.nested-radio {
.ui-radio {
flex-direction: column;

View file

@ -1,5 +1,5 @@
// Components
import vehicleParts from '@/layouts/vehicle-parts/vehicle-parts.vue';
import vehicleParts from '@/layouts/vehicle-parts/vehicle-parts';
import glassPartQuestion from '@/layouts/vehicle-parts/glass-part-question/glass-part-question';
// Supporting Files
@ -82,12 +82,10 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
wrapper.vm.setCmsContent = jest.fn();
// Assert
@ -117,14 +115,12 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
await nextTick();
// Assert
@ -151,12 +147,10 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
await nextTick();
@ -189,20 +183,16 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
wrapper.vm.navigateBack();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
wrapper.vm.$route
);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
wrapper.vm.$route);
});
test('User did not have part questions > navigateBack triggers a router.navigate change with correct scenario', async () => {
@ -221,20 +211,16 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
wrapper.vm.navigateBack();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
wrapper.vm.$route
);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS,
wrapper.vm.$route);
});
test('ForwardButtonAction triggers a router.navigate change if there are child part questions', async () => {
@ -296,22 +282,20 @@ describe('vehicle-parts.vue', () => {
});
wrapper.setData({
selectedGlassParts: { 'Rear-Stationary': { partNumber: 'FW03861GTYN' } },
selectedGlassParts: { 'Rear-Stationary': { partNumber: 'FW03861GTYN' } }
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith('CLICKED_FORWARD_WITH_MOLDING_QUESTIONS', {'query': {'issPage': 'vehicle-parts'}}, {}, {}, expect.anything());
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith('CLICKED_FORWARD_WITH_MOLDING_QUESTIONS', { query: { issPage: 'vehicle-parts' } }, {}, {}, expect.anything());
});
test('ForwardButtonAction triggers a router.navigate change if there are capability questions', async () => {
@ -363,18 +347,16 @@ describe('vehicle-parts.vue', () => {
});
// Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { issPage: 'vehicle-parts' } },
undefined,
(c) => c(wrapper.vm)
);
(c) => c(wrapper.vm));
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith('CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS', {'query': {'issPage': 'vehicle-parts'}}, {}, {}, expect.anything());
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith('CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS', { query: { issPage: 'vehicle-parts' } }, {}, {}, expect.anything());
});
});
@ -401,8 +383,8 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
};
const store = useMainStore();
let actionResult = [];
store.getCapabilityQuestions.mockImplementation(() => Promise.resolve({data: actionResult,}));
const actionResult = [];
store.getCapabilityQuestions.mockImplementation(() => Promise.resolve({ data: actionResult }));
const apiPromise = Promise.resolve(apiResponses);

View file

@ -1,5 +1,8 @@
<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" />
@ -9,24 +12,28 @@
<div class="col">
<div class="select-car-form rounded">
<vehicleBanner
class="mt-2 mb-4"
ref="vehicleBanner"
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader ref="siteSubHeader" cmsWidgetName="SiteSubHeaderWidget" />
<siteSubHeader
ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget" />
<div class="prevent-squish my-5">
<div class="row">
<div class="col">
<alert
id="vehicle-parts-alert"
class="rounded border-0 shadow-sm"
alertClass="alert-warning"
cmsWidgetName="AlertWidget"
:isDismissible="false"
id="vehicle-parts-alert" />
:isDismissible="false" />
</div>
</div>
</div>
<div v-for="(item, i) in PartsOrQuestions" :key="i">
<div
v-for="(item, i) in PartsOrQuestions"
:key="i">
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
<hr v-if="i > 0" />
<glassPartQuestion
@ -38,9 +45,9 @@
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
</div>
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
ref="siteFooter"
:isForwardActionDisabled="isForwardActionDisabled"
@backClicked="navigateBack"
@ForwardClicked="forwardButtonAction" />
@ -73,6 +80,16 @@ import BaseFormMixin from '@/mixins/base-form-mixin.js';
export default {
name: 'vehicle-parts',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form,
glassPartQuestion,
siteHeader,
vehicleBanner,
siteSubHeader,
siteFooter,
alert
},
mixins: [BaseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -96,8 +113,7 @@ export default {
vm.$refs[c][0].initializeComponent({
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget
})
);
}));
});
},
data() {
@ -109,12 +125,12 @@ export default {
},
computed: {
isForwardActionDisabled() {
return this.selectedGlassPartNumbers?.length !== this.PartsFromApi.partsOrQuestions?.length
return this.selectedGlassPartNumbers?.length !== this.PartsFromApi.partsOrQuestions?.length;
},
selectedGlassPartNumbers() {
// Compile all selected parts from the page.
const numberArray = [];
for (let glassPart of Object.values(this.selectedGlassParts)) {
for (const glassPart of Object.values(this.selectedGlassParts)) {
if (glassPart?.partNumber) {
numberArray.push(glassPart.partNumber);
}
@ -125,25 +141,23 @@ export default {
const partsData = this.PartsFromApi;
// Map API result data, to vehicle-parts data structure
const mappedData = partsData.partsOrQuestions?.map((g) => {
return {
glassName: g.glassName,
glassLocation: g.glassLocation,
colorAnswers: g.parts?.reduce((arr, p) => {
arr.push({
ColorAnswerText: p.color,
FeatureAnswers: [
{
FeatureAnswerText:
const mappedData = partsData.partsOrQuestions?.map((g) => ({
glassName: g.glassName,
glassLocation: g.glassLocation,
colorAnswers: g.parts?.reduce((arr, p) => {
arr.push({
ColorAnswerText: p.color,
FeatureAnswers: [
{
FeatureAnswerText:
p.description === '' ? p.color : p.description,
PartNumber: p.partNumber
}
]
});
return arr;
}, [])
};
});
PartNumber: p.partNumber
}
]
});
return arr;
}, [])
}));
return mappedData;
},
@ -156,26 +170,27 @@ export default {
return 'partQuestion';
}
},
mounted() {
this.LoadInitialPartsData();
},
methods: {
arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data)
return (
this.mainStore.damage.isRepair != null &&
this.mainStore.pageData(issPageValues.VEHICLE_PARTS) &&
Object.keys(this.mainStore.pageData(issPageValues.VEHICLE_PARTS)).length !== 0
this.mainStore.damage.isRepair != null
&& this.mainStore.pageData(issPageValues.VEHICLE_PARTS)
&& Object.keys(this.mainStore.pageData(issPageValues.VEHICLE_PARTS)).length !== 0
);
},
async forwardButtonAction() {
const matchedParts = [];
// Match them to the parts from the API.
for (let [key, value] of Object.entries(this.PartsFromApi.partsOrQuestions)) {
for (let [partKey, partValue] of Object.entries(value.parts)) {
for (const [key, value] of Object.entries(this.PartsFromApi.partsOrQuestions)) {
for (const [partKey, partValue] of Object.entries(value.parts)) {
const currentPart = this.PartsFromApi.partsOrQuestions[key].parts[partKey];
const isMatched = this.selectedGlassPartNumbers.some(
(p) => p === currentPart.partNumber
);
const isMatched = this.selectedGlassPartNumbers.some((p) => p === currentPart.partNumber);
if (isMatched) {
matchedParts.push({
@ -197,39 +212,27 @@ export default {
LoadInitialPartsData() {
const partsData = this.PartsFromApi;
this.alreadyPopulatedPartsData =
this.mainStore.lineItems.glassParts === null
? []
: this.mainStore.lineItems.glassParts;
this.alreadyPopulatedPartsData
= this.mainStore.lineItems.glassParts === null
? []
: this.mainStore.lineItems.glassParts;
partsData.partsOrQuestions.map((g) => {
// If the part is already populated, use the value from the store and populate the v-model.
Object.keys(this.alreadyPopulatedPartsData).forEach((key) => {
const partNumber = this.alreadyPopulatedPartsData[key].partNumber;
const { partNumber } = this.alreadyPopulatedPartsData[key];
g.parts.forEach((p) => {
if (p.partNumber === partNumber) {
this.selectedGlassParts[g.glassLocation + '-' + g.glassName] = p;
this.selectedGlassParts[`${g.glassLocation}-${g.glassName}`] = p;
}
});
});
});
}
},
mounted() {
this.LoadInitialPartsData();
},
components: {
Form,
glassPartQuestion,
siteHeader,
vehicleBanner,
siteSubHeader,
siteFooter,
alert
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
#vehicle-parts-alert {
.alert-heading {
margin-top: 0.25rem !important;

View file

@ -1,32 +1,32 @@
<template>
<dropdownQuestion
v-model="selectedValue"
:options="values"
disableAutoFill
placeHolderText="Select an option"
v-model="selectedValue"
:isDisabled="!values.length"
/>
:isDisabled="!values.length" />
</template>
<script>
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question';
export default {
name: 'vehicle-question',
components: {
dropdownQuestion
},
props: {
modelValue: String,
updateValues: Function
},
emits: ['update:modelValue'],
data() {
return {
values: [],
selectedIndex: null
};
},
props: {
modelValue: String,
updateValues: Function,
},
components: {
dropdownQuestion
},
computed: {
selectedValue: {
get() {
@ -43,7 +43,7 @@ export default {
async getNewValues() {
const results = await this.updateValues();
this.values = results?.data;
if (this.values.length == 1) {
if (this.values.length === 1) {
this.selectedValue = 0;
} else {
this.selectedValue = null;

View file

@ -1,5 +1,9 @@
<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 position-relative">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
@ -12,61 +16,54 @@
cmsWidgetName="SiteSubHeaderWidget"
class="siteSubHeader"
justification="center"
darkGraySubText
/>
darkGraySubText />
<vehicleQuestion
ref="vehicleYearQuestion"
class="mb-2 mt-4"
v-model="selectedYear"
class="mb-2 mt-4"
cmsWidgetName="VehicleYearQuestion"
:updateValues="updateYearValues"
validationRules="year-required"
inputId="yearQuestionField"
/>
inputId="yearQuestionField" />
<vehicleQuestion
ref="vehicleMakeQuestion"
class="mb-2 mt-4"
v-model="selectedMake"
class="mb-2 mt-4"
cmsWidgetName="VehicleMakeQuestion"
:updateValues="updateMakeValues"
validationRules="make-required"
inputId="makeQuestionField"
/>
inputId="makeQuestionField" />
<vehicleQuestion
ref="vehicleModelQuestion"
class="mb-2 mt-4"
v-model="selectedModel"
class="mb-2 mt-4"
cmsWidgetName="VehicleModelQuestion"
:updateValues="updateModelValues"
validationRules="model-required"
inputId="modelQuestionField"
/>
inputId="modelQuestionField" />
<vehicleQuestion
ref="vehicleStyleQuestion"
class="mb-2 mt-4"
v-model="selectedStyle"
class="mb-2 mt-4"
cmsWidgetName="VehicleStyleQuestion"
:updateValues="updateStyleValues"
validationRules="style-required"
inputId="styleQuestionField"
/>
inputId="styleQuestionField" />
<vehicleBanner
ref="banner"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="displayGeneric"
class="mt-5 mb-3"
ref="banner"
/>
class="mt-5 mb-3" />
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
@ForwardClicked="forwardButtonAction"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
ref="siteFooter"
/>
@ForwardClicked="forwardButtonAction"
@back-clicked="backButtonAction" />
</div>
</div>
</div>
@ -83,14 +80,14 @@ 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 vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner';
import vehicleQuestion from '@/layouts/vehicle-selection/vehicle-question/vehicle-question.vue';
import vehicleQuestion from '@/layouts/vehicle-selection/vehicle-question/vehicle-question';
import { useMainStore } from '@/store';
// Supporting files
import baseFormMixin from '@/mixins/base-form-mixin.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { settleAllPromises } from '@/helpers/layout-helper';
import { Form, defineRule, useForm } from 'vee-validate';
import { Form, defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
import { errorMessages } from '@/constants/error-messages';
@ -102,22 +99,16 @@ defineRule('style-required', required(errorMessages.STYLE_REQUIRED));
export default {
name: 'vehicle-selection',
components: {
siteHeader,
siteSubHeader,
vehicleBanner,
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleQuestion
},
mixins: [baseFormMixin],
data() {
return {
selectedYear: null,
selectedMake: null,
selectedModel: null,
selectedStyle: null
};
},
props: {
cmsWidgetName:String,
validationRules:String
},
mounted() {
this.$refs['vehicleYearQuestion'].getNewValues();
},
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -131,34 +122,51 @@ export default {
}
];
let resultMap = await settleAllPromises(promiseResultMap);
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
props: {
cmsWidgetName: String,
validationRules: String
},
data() {
return {
selectedYear: null,
selectedMake: null,
selectedModel: null,
selectedStyle: null
};
},
computed: {
displayGeneric() {
return !this.selectedStyle;
}
},
watch: {
selectedYear(value) {
this.mainStore.updateVehicleYear(value);
this.$refs['vehicleMakeQuestion'].clearValues();
this.$refs.vehicleMakeQuestion.clearValues();
if (value) {
this.$refs['vehicleMakeQuestion'].getNewValues();
this.$refs.vehicleMakeQuestion.getNewValues();
}
},
selectedMake(value) {
this.mainStore.updateVehicleMake(value);
this.$refs['vehicleModelQuestion'].clearValues();
this.$refs.vehicleModelQuestion.clearValues();
if (value) {
this.$refs['vehicleModelQuestion'].getNewValues();
this.$refs.vehicleModelQuestion.getNewValues();
}
},
selectedModel(value) {
this.mainStore.updateVehicleModel(value);
this.$refs['vehicleStyleQuestion'].clearValues();
this.$refs.vehicleStyleQuestion.clearValues();
if (value) {
this.$refs['vehicleStyleQuestion'].getNewValues();
this.$refs.vehicleStyleQuestion.getNewValues();
}
},
selectedStyle(value) {
@ -166,6 +174,9 @@ export default {
this.mainStore.setVehicle();
}
},
mounted() {
this.$refs.vehicleYearQuestion.getNewValues();
},
methods: {
arePagePrerequisitesValid() {
return true;
@ -179,10 +190,8 @@ export default {
navigateForward() {
this.mainStore.setVehicle().then(() => {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD,
this.$route);
});
},
async updateYearValues() {
@ -197,24 +206,11 @@ export default {
async updateStyleValues() {
return await this.mainStore.getVehicleStyles();
}
},
computed: {
displayGeneric() {
return !this.selectedStyle;
}
},
components: {
siteHeader,
siteSubHeader,
vehicleBanner,
siteFooter,
Form,
vehicleQuestion
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.select-car-form {
margin-left: .75rem;
margin-right: .75rem;

View file

@ -4,8 +4,8 @@ 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 VinLocationInformationComponent from './vin-location-information.vue';
import { GaActions } from '@/constants/analytics';
import VinLocationInformationComponent from '@/layouts/vin-lookup/vin-location-information/vin-location-information';
const mockText = Object.freeze({
HEADER: 'Mock Header',

View file

@ -3,35 +3,30 @@
<div
class="vin-toggle"
:class="[isVinLocationDetailVisible ? 'active' : '']"
@click="handleClickToggle"
>
@click="handleClickToggle">
<textLink
linkType="text"
href="#!"
:text="textForToggle"
/>
:text="textForToggle" />
</div>
<Transition>
<div
v-show="isVinLocationDetailVisible"
id="vin-location-detail-wrapper"
class="mt-2"
>
class="mt-2">
<div
v-html="vinLocationDetailContent"
id="vin-location-detail-content"
class="mb-2"
/>
v-html="vinLocationDetailContent" />
<img
:src="vinLocationDetailImage"
alt="VIN Locations"
>
alt="VIN Locations">
</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: 'vin-location-information',

View file

@ -1,14 +1,13 @@
<template>
<alert
id="perfect-match-alert"
alertClass="alert-success"
cmsWidgetName="AlertPerfectMatchInsuranceNotVerified"
:manualCopy="body"
id="perfect-match-alert"
aria-label="perfect-match-alert"
/>
aria-label="perfect-match-alert" />
</template>
<script>
import alert from '@/ux-components/alert/alert.vue';
import alert from '@/ux-components/alert/alert';
import { getDamageString } from '@/helpers/damage-helper';
export default {

View file

@ -4,13 +4,12 @@
cmsWidgetName="AlertMatchedTwoIdenticalYMMVehicleWidget"
:manualCopy="body"
:manualHeadline="header"
aria-label="two-identical-ymm-vehicle-alert"
/>
aria-label="two-identical-ymm-vehicle-alert" />
</template>
<script>
import { getDamageString } from '@/helpers/damage-helper';
import alert from '@/ux-components/alert/alert.vue';
import alert from '@/ux-components/alert/alert';
export default {
name: 'two-identical-ymm-vehicle-alert',

View file

@ -1,13 +1,12 @@
<template>
<alert
id="vehicle-not-found-alert"
alertClass="alert-danger"
cmsWidgetName="AlertVinNotFoundWidget"
id="vehicle-not-found-alert"
aria-label="vehicle-not-found-alert"
/>
aria-label="vehicle-not-found-alert" />
</template>
<script>
import alert from '@/ux-components/alert/alert.vue';
import alert from '@/ux-components/alert/alert';
export default {
name: 'vehicle-not-found-alert',

View file

@ -4,13 +4,12 @@
cmsWidgetName="AlertMatchedDifferentVehicleWidget"
:manualCopy="body"
:manualHeadline="header"
aria-label="vehicle-not-matched-alert"
/>
aria-label="vehicle-not-matched-alert" />
</template>
<script>
import { getDamageString } from '@/helpers/damage-helper';
import alert from '@/ux-components/alert/alert.vue';
import alert from '@/ux-components/alert/alert';
export default {
name: 'vehicle-not-matched-alert',

View file

@ -4,7 +4,7 @@ import vehicleLookupAlertTypes from '@/constants/vehicle-lookup-alert-types';
import { RouterLinkStub } from '@vue/test-utils';
import { render } from '@testing-library/vue';
import '@testing-library/jest-dom';
import VinLookupAlertsComponent from './vin-lookup-alerts.vue';
import VinLookupAlertsComponent from '@/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts';
jest.mock('@/helpers/damage-helper');
@ -18,7 +18,7 @@ const mountOptions = {
methods: {
getCmsContent: jest.fn(() => ''),
getFooterInfoBoxHeight: jest.fn(() => 80),
cssClassNameForCmsWidget: jest.fn(() => 'widget-name-mock-class'),
cssClassNameForCmsWidget: jest.fn(() => 'widget-name-mock-class')
}
}
]

View file

@ -1,28 +1,24 @@
<template>
<div
id="vin-lookup-alerts-wrapper"
:class="wrapperCssClass"
>
:class="wrapperCssClass">
<vehicleNotFoundAlert
v-if="isVehicleNotFoundVisible"
/>
v-if="isVehicleNotFoundVisible" />
<twoIdenticalYMMVehicleAlert
v-if="isTwoIdenticalYMMVehicleFoundVisible"
/>
v-if="isTwoIdenticalYMMVehicleFoundVisible" />
<vehicleNotMatchedAlert
v-else-if="isVehicleNotMatchedVisible"
/>
v-else-if="isVehicleNotMatchedVisible" />
<perfectMatchAlert
v-else-if="isPerfectMatchVisible"
/>
v-else-if="isPerfectMatchVisible" />
</div>
</template>
<script>
import vehicleLookupAlertTypes from '@/constants/vehicle-lookup-alert-types';
import vehicleNotFoundAlert from '@/layouts/vin-lookup/vin-lookup-alerts/vehicle-not-found-alert/vehicle-not-found-alert';
import vehicleNotMatchedAlert from '@/layouts/vin-lookup/vin-lookup-alerts/vehicle-not-matched-alert/vehicle-not-matched-alert';
import twoIdenticalYMMVehicleAlert from '@/layouts/vin-lookup/vin-lookup-alerts/two-identical-ymm-vehicle-alert/two-identical-ymm-vehicle-alert'
import perfectMatchAlert from '@/layouts/vin-lookup/vin-lookup-alerts//perfect-match-alert/perfect-match-alert.vue';
import twoIdenticalYMMVehicleAlert from
'@/layouts/vin-lookup/vin-lookup-alerts/two-identical-ymm-vehicle-alert/two-identical-ymm-vehicle-alert';
import perfectMatchAlert from '@/layouts/vin-lookup/vin-lookup-alerts//perfect-match-alert/perfect-match-alert';
export default {
name: 'vin-lookup-alerts',

View file

@ -11,7 +11,7 @@ import { GaActions } from '@/constants/analytics';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
import { routerParams } from '@/router/router-params';
import { useMainStore } from '@/store';
import VinLookupComponent from './vin-lookup.vue';
import VinLookupComponent from '@/layouts/vin-lookup/vin-lookup';
const continueButtonQuerySelector = '[data-test-id="site-footer-main-button"]';
const errorMessageWrapperElSelector = '#vin-question-wrapper .form-test-error';

View file

@ -1,5 +1,9 @@
<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" />
@ -8,12 +12,27 @@
<div class="row px-3">
<div class="col">
<div class="select-car-form rounded">
<vehicleBanner class="mt-2 mb-4" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<vinLookupAlerts class="mt-5" :activeAlertType="activeVehicleLookupAlertType" />
<vinQuestion v-model="vin" :mask="vinMask" :isDisabled="vinPopulatedOnPageLoad" textPosition="left" />
<vinLookupAlerts
class="mt-5"
:activeAlertType="activeVehicleLookupAlertType" />
<vinQuestion
v-model="vin"
:mask="vinMask"
:isDisabled="vinPopulatedOnPageLoad"
textPosition="left" />
<vinLocationInformation />
<siteFooter cmsWidgetName="SiteFooterWidget" :isForwardActionDisabled="!meta.valid" @backClicked="backButtonAction" @forwardClicked="forwardButtonAction" ref="siteFooter" class="mt-5" />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
class="mt-5"
@backClicked="backButtonAction"
@forwardClicked="forwardButtonAction" />
</div>
</div>
</div>
@ -37,13 +56,13 @@ import { useMainStore } from '@/store';
import baseFormMixin from '@/mixins/base-form-mixin';
import vehicleQuestionsMixin from '@/mixins/vehicle-questions-mixin';
import { Form } 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 vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import vinLocationInformation from './vin-location-information/vin-location-information.vue';
import vinLookupAlerts from './vin-lookup-alerts/vin-lookup-alerts.vue';
import vinQuestion from './vin-question/vin-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 vehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner';
import vinLocationInformation from '@/layouts/vin-lookup/vin-location-information/vin-location-information';
import vinLookupAlerts from '@/layouts/vin-lookup/vin-lookup-alerts/vin-lookup-alerts';
import vinQuestion from '@/layouts/vin-lookup/vin-question/vin-question';
export default {
name: 'vin-lookup',
@ -51,6 +70,7 @@ export default {
siteFooter,
siteHeader,
siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
vehicleBanner,
vinLocationInformation,
@ -60,23 +80,7 @@ export default {
mixins: [baseFormMixin, vehicleQuestionsMixin],
provide() {
return {
vehicleFromLookup: computed(() => this.vehicleFromLookup),
};
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
activeVehicleLookupAlertType: null,
needToLookupVehicle: true,
vehicleFromLookup: null,
vin: this.getVinFromStore(),
forwardButtonCarStyle: '',
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
bailout: false
vehicleFromLookup: computed(() => this.vehicleFromLookup)
};
},
async beforeRouteEnter(to, from, next) {
@ -96,6 +100,22 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
});
},
setup() {
const mainStore = useMainStore();
return { mainStore };
},
data() {
return {
activeVehicleLookupAlertType: null,
needToLookupVehicle: true,
vehicleFromLookup: null,
vin: this.getVinFromStore(),
forwardButtonCarStyle: '',
vinPopulatedOnPageLoad: this.getVinFromStore()?.length > 0,
bailout: false
};
},
computed: {
isCarIdDifferentFromTheStore() {
return (
@ -105,18 +125,19 @@ export default {
},
isTwoIdenticalYMMVehicleFound() {
const vinYmmFound = `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model}`;
const vinYmmExpected = `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return(vinYmmFound.toLowerCase()==vinYmmExpected.toLowerCase());
const vinYmmExpected
= `${this.mainStore.order.vehicle.year} ${this.mainStore.order.vehicle.make} ${this.mainStore.order.vehicle.model}`;
return (vinYmmFound.toLowerCase() === vinYmmExpected.toLowerCase());
},
vinMask() {
if (this.vinPopulatedOnPageLoad) {
// TODO: Modify to remove side effects in computed
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.PERFECT_MATCH;
this.needToLookupVehicle = false;
const lastSixChars = this.vin.substring(11, this.vin.length);
return `!X!X!X!X!X!X!X!X!X!X!X${lastSixChars}`;
} else {
return 'XXXXXXXXXXXXXXXXX';
}
return 'XXXXXXXXXXXXXXXXX';
}
},
watch: {
@ -124,9 +145,7 @@ export default {
this.resetActiveAlert();
this.$refs.siteFooter.enableForwardAction();
this.needToLookupVehicle = true;
this.$refs.siteFooter.updateButtonText(
this.getCmsContent('SiteFooterWidget', 'ForwardButtonText')
);
this.$refs.siteFooter.updateButtonText(this.getCmsContent('SiteFooterWidget', 'ForwardButtonText'));
}
},
methods: {
@ -163,8 +182,7 @@ export default {
}
if (this.bailout) {
return this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
return this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
this.$route);
}
// Add vin bcs the response from the service doesn't contain vin
@ -179,13 +197,14 @@ export default {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NOT_MATCHED;
}
const vehicleYearMakeModelStyle = `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model} ${this.forwardButtonCarStyle}`;
const vehicleYearMakeModelStyle
= `${this.vehicleFromLookup.year} ${this.vehicleFromLookup.make} ${this.vehicleFromLookup.model} ${this.forwardButtonCarStyle}`;
this.$refs.siteFooter.updateButtonText(`Continue with ${vehicleYearMakeModelStyle}`);
this.$refs.siteFooter.removeLoader();
this.needToLookupVehicle = false;
return;
return null;
}
let isSelectedGlassAvailableForVehicle = true;
@ -198,12 +217,10 @@ export default {
if (this.isCarIdDifferentFromTheStore && !isSelectedGlassAvailableForVehicle) {
this.mainStore.updateVehicle(this.vehicleFromLookup);
this.mainStore.resetDamageState();
this.$router.navigate(
this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$router.navigate(this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route,
{},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
);
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true });
// navigate() doesn't stop the processing flow
return;
@ -219,7 +236,7 @@ export default {
// To Do: Need requirement on what to do here
console.error('Error on retrieving PartsOrQuestions');
this.$refs.siteFooter.removeLoader();
return;
return null;
}
// Comes from vehicleQuestionsMixin.navigateForward()
@ -233,20 +250,19 @@ export default {
} catch (responseError) {
return {
error: {
status: responseError.status,
},
status: responseError.status
}
};
}
},
async lookupVehicleByVin(vin) {
try {
return await this.mainStore.lookupVehicleByVin(vin);
}
catch (responseError) {
} catch (responseError) {
return {
error: {
status: responseError.status,
},
status: responseError.status
}
};
}
},
@ -256,7 +272,7 @@ export default {
resetVehicleFromLookup() {
this.vehicleFromLookup = null;
},
resetDependentState() {},
},
resetDependentState() {}
}
};
</script>

View file

@ -1,14 +1,13 @@
<template>
<textboxQuestion
id="vin-question-wrapper"
v-model="vin"
cmsWidgetName="VinNumberQuestionWidget"
disableAutoFill
id="vin-question-wrapper"
inputId="vin-question"
isRequired
maxLength="17"
validationRules="vin-required|vin-format"
/>
validationRules="vin-required|vin-format" />
</template>
<script>
// Import Other Supporting File(s)
@ -17,20 +16,20 @@ import { regex, required } from '@/helpers/validation-rules';
import { errorMessages } from '@/constants/error-messages';
// Import Component(s)
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question';
defineRule('vin-required', required(errorMessages.VIN_REQUIRED));
defineRule('vin-format', regex(/^[a-hA-Hj-nJ-NpPr-zR-Z0-9]{17}$/, errorMessages.VIN_FORMAT));
export default {
name: 'vin-question',
components: {
textboxQuestion
},
props: {
modelValue: String
},
emits: ['update:modelValue'],
components: {
textboxQuestion
},
computed: {
vin: {
get() {

View file

@ -1,4 +1,4 @@
import welcomePage from '@/layouts/welcome-page/welcome-page.vue';
import welcomePage from '@/layouts/welcome-page/welcome-page';
// Supporting files
// Supporting files
@ -110,13 +110,11 @@ describe('navigation', () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
undefined,
{},
{},
mockvehicles
);
mockvehicles);
});
test('if policy is found, but no vehicles, navigate to vehicle-selection page', async () => {
// Arrange
@ -138,10 +136,8 @@ describe('navigation', () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
undefined
);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
undefined);
});
test('if policy is not found, navigate to policy-holder-details page', async () => {
// Arrange
@ -158,10 +154,8 @@ describe('navigation', () => {
await wrapper.vm.navigateForward();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
undefined
);
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
undefined);
});
});
@ -172,31 +166,27 @@ function setupMocks({
policies = [],
getCoveragePolicyInfoResponse
}) {
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => {
return Promise.resolve({
data: getCoveragePolicyInfoResponse
? getCoveragePolicyInfoResponse
: {
policies: [
useMainStore().getCoveragePolicyInfo = jest.fn().mockImplementation(() => Promise.resolve({
data: getCoveragePolicyInfoResponse || {
policies: [
{
insureds: [
{
insureds: [
{
customerId: 'TEST_ID'
}
],
vehicles: [
{
vin: 'TEST_VIN'
},
{
vin: 'TEST_VIN2'
}
]
customerId: 'TEST_ID'
}
],
vehicles: [
{
vin: 'TEST_VIN'
},
{
vin: 'TEST_VIN2'
}
]
}
});
});
]
}
}));
// Mock api responses
const apiResponses = {
@ -213,7 +203,7 @@ function setupMocks({
SiteFooterWidget: pageFooterWidgetCmsContent
},
policyLookupResponse: {
policies: policies
policies
}
};

View file

@ -1,79 +1,88 @@
<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 welcome">
<div class="fade-on-route-transition position-relative">
<siteHeader cmsWidgetName="SiteHeaderWidget"/>
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" class="mt-4"/>
<siteHeader cmsWidgetName="SiteHeaderWidget" />
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<div class="container-fluid px-5">
<div class="row mt-5">
<div class="col">
<textboxQuestion
ref="policyNumber"
v-model="welcomePageModel.policyNumber"
inputId="policyNumberField"
cmsWidgetName="PolicyNumberQuestion"
v-model="welcomePageModel.policyNumber"
isRequired
ref="policyNumber"
disableAutoFill
:isDisabled="isPolicyHolderEnabled"
:validationRules="rules.policyNumber" />
</div>
</div>
<div class="row mt-4" v-if="this.isCoverageEnabled">
<div
v-if="isCoverageEnabled"
class="row mt-4">
<div class="col">
<textboxQuestion
ref="policyZip"
v-model="welcomePageModel.policyZipCode"
inputId="policyZipCode"
cmsWidgetName="PolicyZipQuestion"
v-model="welcomePageModel.policyZipCode"
isRequired
ref="policyZip"
:validationRules="rules.policyZip" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<textboxQuestion
ref="dateOfLoss"
v-model="welcomePageModel.dateOfLoss"
type="date"
cmsWidgetName="DateOfLossQuestion"
v-model="welcomePageModel.dateOfLoss"
inputId="dateOfLossField"
isRequired
ref="dateOfLoss"
disableAutoFill
:max="new Date().toJSON().slice(0,10)"
:min="'1972-12-01'"
:validationRules="rules.lossDate"
/>
:validationRules="rules.lossDate" />
</div>
</div>
<div class="row px-4">
<div class="col px-0">
<textBlock cmsWidgetName="DamageDateEstimateWidget" typeStyle="small" class="mt-2" />
<textBlock
cmsWidgetName="DamageDateEstimateWidget"
typeStyle="small"
class="mt-2" />
</div>
</div>
<div class="row mt-4">
<div class="col">
<dropdownQuestion
cmsWidgetName="DamageCauseQuestion"
v-model="welcomePageModel.damageCause"
id="welcomeDropdown"
ref="damageCause"
v-model="welcomePageModel.damageCause"
cmsWidgetName="DamageCauseQuestion"
inputId="damageCauseQuestionField"
:options="DamageCauseOptions"
disableAutoFill
:validationRules="rules.damageOption"
placeHolderText="Select an option"
id="welcomeDropdown"
/>
placeHolderText="Select an option" />
</div>
</div>
<div class="row mt-4 ">
<div class="col">
<textboxQuestion
ref="phoneNumber"
v-model="welcomePageModel.phoneNumber"
inputId="phoneNumberField"
cmsWidgetName="PhoneNumberQuestion"
v-model="welcomePageModel.phoneNumber"
:validationRules="rules.phoneNumber"
isRequired
ref="phoneNumber"
mask="###-###-####"
disableAutoFill />
</div>
@ -81,10 +90,10 @@
<div class="row mt-4">
<div class="col">
<textboxQuestion
ref="email"
v-model="welcomePageModel.email"
inputId="emailField"
cmsWidgetName="EmailAddressQuestion"
v-model="welcomePageModel.email"
ref="email"
:validationRules="rules.email"
isRequired
disableAutoFill />
@ -93,40 +102,41 @@
<div class="row">
<div class="col">
<textboxQuestion
v-if="displayDamageCityQuestion"
ref="damageCity"
v-model="welcomePageModel.damageCity"
class="mt-4"
inputId="damageCityField"
cmsWidgetName="DamageCityQuestion"
v-model="welcomePageModel.damageCity"
isRequired
ref="damageCity"
disableAutoFill
v-if="this.displayDamageCityQuestion"
:validationRules="rules.lossCity" />
</div>
</div>
<div class="row">
<div class="col">
<dropdownQuestion
v-if="displayDamageStateQuestion"
id="welcomeDropdown"
ref="state"
v-model="welcomePageModel.damageState"
class="mt-4"
cmsWidgetName="DamageStateQuestion"
v-model="welcomePageModel.damageState"
ref="state"
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="getStates"
:validationRules="rules.lossState"
isRequired
disableAutoFill
v-if="this.displayDamageStateQuestion"
placeHolderText="Select an option"
id="welcomeDropdown"
/>
placeHolderText="Select an option" />
</div>
</div>
<div class="row mb-3">
<buttonQuestion
v-if="displayGlassOnlyQuestion"
ref="glassOnlyDamage"
v-model="welcomePageModel.isDamageGlassOnly"
class="px-0 mt-4"
cmsWidgetName="GlassOnlyQuestion"
v-model="welcomePageModel.isDamageGlassOnly"
inputId="isDamageGlassOnly"
:answers="DamageGlassOnlyOptions"
:questionText="DamageGlassOnlyQuestion"
@ -134,11 +144,11 @@
:validationRules="rules.damageOption"
isRequired
isSmallQuestionLabelText
ref="glassOnlyDamage"
v-if="this.displayGlassOnlyQuestion"
disableAutoFill/>
disableAutoFill />
</div>
<div class="row position-sticky top-100" id="welcomeFooter">
<div
id="welcomeFooter"
class="row position-sticky top-100">
<div class="col">
<siteFooter
class="mt-3"
@ -172,7 +182,7 @@ import { required, regex } from '@/helpers/validation-rules';
import { errorMessages } from '@/constants/error-messages';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
import { states } from '@/constants/states'
import { states } from '@/constants/states';
import globalRules from '@/constants/global-rules';
// define validation rules
@ -186,7 +196,45 @@ defineRule('policy-zip-format', regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages
export default {
name: 'welcome-page',
components: {
siteHeader,
siteSubHeader,
buttonQuestion,
textboxQuestion,
dropdownQuestion,
siteFooter,
textBlock,
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
setup() {
const mainStore = useMainStore();
// Set order account number from the issConfig.
mainStore.order.accountNumber = mainStore.issConfig.accountNumber;
return { mainStore };
},
data() {
return {
welcomePageModel: this.getWelcomePageModelFromStore(),
@ -203,32 +251,43 @@ export default {
}
};
},
setup() {
const mainStore = useMainStore();
// Set order account number from the issConfig.
mainStore.order.accountNumber = mainStore.issConfig.accountNumber;
return { mainStore };
},
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
computed: {
DamageCauseOptions() {
const damageCauseAnswers = this.getCmsContent('DamageCauseQuestion', 'Answers');
const damageCauseAnswersObj = {};
if (damageCauseAnswers) {
for (const answer of Object.values(damageCauseAnswers)) {
if (answer?.Name) {
damageCauseAnswersObj[answer.Name] = answer.Name;
}
}
}
];
// use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
return damageCauseAnswersObj;
},
DamageGlassOnlyOptions() {
return this.getCmsContent('GlassOnlyQuestion', 'Answers');
},
DamageGlassOnlyQuestion() {
return this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
},
displayDamageCityQuestion() {
return !!this.getCmsContent('DamageCityQuestion', 'QuestionText');
},
displayDamageStateQuestion() {
return !!this.getCmsContent('DamageStateQuestion', 'QuestionText');
},
displayGlassOnlyQuestion() {
return !!this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
},
getStates() {
return states;
},
isPolicyHolderEnabled() {
return !!this.mainStore.issConfig.disabledFields.policyNumber;
},
isCoverageEnabled() {
return this.mainStore.issConfig.isCoverageEnabled;
}
},
methods:
{
@ -282,27 +341,21 @@ export default {
if (policy) {
if (this.vehiclesFound) {
// if policy lookup is successful and vehicles are found, navigate to policy-vehicles page
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_WITH_VEHICLES,
this.$route,
{},
{},
this.vehiclesFound
);
this.vehiclesFound);
} else {
// if policy lookup is successful, but no vehicles are associated with the policy
// navigate to vehicle-selection page (manual entry)
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
this.$route);
}
} else {
// if policy lookup is unsuccessful, navigate to policy-holder-details page
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
this.$route
);
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
this.$route);
}
},
@ -318,58 +371,10 @@ export default {
phoneNumber: this.mainStore.order.customer.phoneNumber,
email: this.mainStore.order.customer.emailAddress,
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
}
};
}
},
computed:{
DamageCauseOptions() {
const damageCauseAnswers = this.getCmsContent('DamageCauseQuestion', 'Answers');
const damageCauseAnswersObj = {};
if (damageCauseAnswers) {
for (let answer of Object.values(damageCauseAnswers)) {
if (answer?.Name) {
damageCauseAnswersObj[answer.Name] = answer.Name;
}
}
}
return damageCauseAnswersObj;
},
DamageGlassOnlyOptions() {
return this.getCmsContent('GlassOnlyQuestion', 'Answers');
},
DamageGlassOnlyQuestion() {
return this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
},
displayDamageCityQuestion() {
return !!this.getCmsContent('DamageCityQuestion', 'QuestionText');
},
displayDamageStateQuestion() {
return !!this.getCmsContent('DamageStateQuestion', 'QuestionText');
},
displayGlassOnlyQuestion() {
return !!this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
},
getStates() {
return states;
},
isPolicyHolderEnabled() {
return !!this.mainStore.issConfig.disabledFields.policyNumber;
},
isCoverageEnabled() {
return this.mainStore.issConfig.isCoverageEnabled;
}
},
components: {
siteHeader,
siteSubHeader,
buttonQuestion,
textboxQuestion,
dropdownQuestion,
siteFooter,
textBlock,
Form,
}
}
};
</script>
<style lang="scss">

View file

@ -1,6 +1,6 @@
import { createWebHistory, createRouter } from 'vue-router';
import { lazyLoadComponent } from '@/router/dynamic-routing/component-loader';
import {issPageValues} from '@/router/router-constants/issPage-values';
import { issPageValues } from '@/router/router-constants/issPage-values';
import { routingTable } from '@/router/router-constants/routing-table';
import { useMainStore } from '@/store';
import eventBus from '@/helpers/event-bus/event-bus';
@ -25,7 +25,7 @@ const routes = [
to.query.issPage = !to.query.issPage ? issPageValues.WELCOME_PAGE : to.query.issPage;
// Do not run these for the main entry page - as it is not part of the user flow.
if (to.query.issPage != issPageValues.ENTRY_PAG ) {
if (to.query.issPage !== issPageValues.ENTRY_PAG) {
if (analyticsMixin.methods.noSession()) {
await analyticsMixin.methods.initSession();
} else {
@ -45,7 +45,7 @@ const routes = [
const routeData = await GetRouteInfoFromPageName(to.query.issPage);
if (routeData[0].name.toLowerCase() === 'error') {
throw('Page not found!');
throw new Error('Page not found!');
}
// Add our dynamic route.
@ -74,7 +74,7 @@ const router = createRouter({
routes,
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 }
return { top: 0 };
}
});
@ -84,7 +84,7 @@ router.afterEach((to, from) => { /*eslint-disable-line*/
// Update lastPageVisited in the store
store.updateLastPageVisited(to.name);
if (to.query.issPage != issPageValues.ENTRY_PAGE) {
if (to.query.issPage !== issPageValues.ENTRY_PAGE) {
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA();
@ -99,7 +99,7 @@ async function GetRouteInfoFromPageName(pageName) {
const response = await useMainStore().getRouteInfo(pageName);
const jsonFromResponse = JSON.parse(response.data.Result);
let routeData = [];
const routeData = [];
// Add our route data and return our array.
Object.keys(jsonFromResponse).forEach((key) => {
@ -114,29 +114,25 @@ async function GetRouteInfoFromPageName(pageName) {
}
// Use this navigation when you need to call next() explicitly. beforeRouteEnter is a good example.
router.overrideNavigation = (
scenario,
router.overrideNavigation = (scenario,
currentRoute,
next,
isSavingNavigation,
optionalQuery = {},
optionalParams = {},
optionalPageData
) => {
navigate(
scenario,
optionalPageData) => {
navigate(scenario,
currentRoute,
isSavingNavigation,
optionalQuery,
optionalParams,
optionalPageData
);
optionalPageData);
next();
};
router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) => {
navigate(scenario, currentRoute, optionalQuery, optionalParams, optionalPageData);
}
};
// Navigate to the next route, depending on the scenario.
function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams = {}, optionalPageData = {}) { /*eslint-disable-line*/
@ -156,14 +152,13 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams =
if (matchingScenarioMap.destinationIssPageValue) {
// Update page data to the store for next page if provided. Otherwise, keep existing page data or set to empty object
const existingPageDataForPage = useMainStore().pageData(matchingScenarioMap.destinationIssPageValue);
baseMixin.methods.savePageDataToStore(
matchingScenarioMap.destinationIssPageValue,
baseMixin.methods.savePageDataToStore(matchingScenarioMap.destinationIssPageValue,
Object.keys(optionalPageData).length > 0
? optionalPageData
: existingPageDataForPage ?? {}
);
: existingPageDataForPage ?? {});
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our issPage one.
// We're always pushing the same path, just changing query strings.
// Make sure our optional query strings get combined with our issPage one.
router.push({
name: 'root',
query: Object.assign(optionalQuery, {
@ -174,7 +169,7 @@ function navigate (scenario, currentRoute, optionalQuery = {}, optionalParams =
} else if (matchingScenarioMap.destinationUrl) {
navigateToUrl(matchingScenarioMap.destinationUrl, optionalQuery);
}
};
}
// Navigate to an external url.
function navigateToUrl(url, optionalQuery = {}) {
@ -185,28 +180,25 @@ function navigateToUrl(url, optionalQuery = {}) {
}
window.location.assign(externalUrl);
};
}
// Get navigation map depending on the scenario and the current 'page' you're on.
function getNavigationMap (scenario, currentRoute) {
function getNavigationMap(scenario, currentRoute) {
const issPageValue = currentRoute.query.issPage;
try {
const matchedQueryValue = routingTable(useMainStore())
.filter(
(item) =>
item.issPageValue === issPageValue &&
item.maps.filter((map) => map.scenario === scenario).length > 0
);
.filter((item) =>
item.issPageValue === issPageValue
&& item.maps.filter((map) => map.scenario === scenario).length > 0);
let maps = matchedQueryValue ? matchedQueryValue.map((m) => m.maps.filter((map) => map.scenario === scenario))[0] : undefined;
const maps = matchedQueryValue ? matchedQueryValue.map((m) => m.maps.filter((map) => map.scenario === scenario))[0] : undefined;
return maps ? maps.filter(x => x.filter === true || x.filter === undefined)[0] : undefined;
}
catch (e) {
return maps ? maps.filter((x) => x.filter === true || x.filter === undefined)[0] : undefined;
} catch (e) {
console.error(e);
return undefined;
}
};
}
function GoToStartOn404(next) {
const errorPageName = issPageValues.WELCOME_PAGE;
@ -217,22 +209,20 @@ function GoToStartOn404(next) {
});
// Put item on the bus
eventBus.addEventToBus(
globalEvents.Categories.GLOBAL_ALERT,
eventBus.addEventToBus(globalEvents.Categories.GLOBAL_ALERT,
globalEvents.SubCategories.PAGE_NOT_FOUND,
{
isDismissible: true,
messageCopy: 'You can get a quote by starting on this page.',
messageHeadline: "We're sorry, something went wrong.",
type: globalEventTypes.Danger
}
);
});
next({
name: errorPageName,
query: {issPage: errorPageName }
query: { issPage: errorPageName }
});
};
}
// Run SiteEntry and PageEntry triggers for experiments
async function runExperiments(nextPage) {

View file

@ -19,7 +19,7 @@ export const issPageValues = {
REVIEW_PAGE: 'review-page',
REVEAL: 'reveal',
SERVICE_LOCATION: 'service-location',
TPA_CONFIRMATION:'tpa-confirmation',
TPA_CONFIRMATION: 'tpa-confirmation',
SERVICE_PACKAGES: 'service-packages',
SCHEDULE_PAGE: 'schedule-page',
VEHICLE_DAMAGE: 'vehicle-damage',
@ -28,7 +28,6 @@ export const issPageValues = {
VEHICLE_SELECTION: 'vehicle-selection',
VIN_LOOKUP: 'vin-lookup',
TPA_SUBMIT: 'tpa-submit',
BAILOUT_PAGE: 'bailout-page',
TPA_SEARCH: 'tpa-search',
POLICY_VEHICLES:'policy-vehicles'
POLICY_VEHICLES: 'policy-vehicles'
};

View file

@ -18,7 +18,6 @@ const navigationScenarios = {
SELECTED_STYLE: 'SELECTED_STYLE',
// Policy Holder Details
CLICKED_FORWARD_POLICY_UNVERIFIED: 'CLICKED_FORWARD_POLICY_UNVERIFIED',
CLICKED_FORWARD_POLICY_VERIFIED: 'CLICKED_FORWARD_POLICY_VERIFIED',
// Policy Vehicle

View file

@ -2,8 +2,8 @@ import { issPageValues } from '@/router/router-constants/issPage-values';
import { navigationScenarios } from '@/router/router-constants/navigation-scenarios';
// Get store from router/index.js instead of importing it here to get updated values
const routingTable = function (store) {
return [
const routingTable = () => [
{
issPageValue: issPageValues.VEHICLE_SELECTION,
maps: [
@ -556,43 +556,42 @@ const routingTable = function (store) {
}
]
},
{
issPageValue: issPageValues.TPA_SUBMIT,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.TPA_SEARCH
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_CONFIRMATION
}
]
},
{
issPageValue: issPageValues.ORDER_CONFIRMATION,
maps: [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.WELCOME_PAGE
}
]
},
{
issPageValue: issPageValues.TPA_SEARCH,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_SUBMIT
}
]
}
];
{
issPageValue: issPageValues.TPA_SUBMIT,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.TPA_SEARCH
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_CONFIRMATION
}
]
},
{
issPageValue: issPageValues.ORDER_CONFIRMATION,
maps: [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.WELCOME_PAGE
}
]
},
{
issPageValue: issPageValues.TPA_SEARCH,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.PROVIDER_PREFERENCE
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.TPA_SUBMIT
}
]
}
];
};
export { routingTable };
export { routingTable };

View file

@ -13,12 +13,12 @@ describe('Router', () => {
});
it('Should push next view when navigating locally', () => {
let scenario = navigationScenarios.CLICKED_FORWARD;
let currentRoute = { query: { issPage: issPageValues.WELCOME_PAGE }};
const scenario = navigationScenarios.CLICKED_FORWARD;
const currentRoute = { query: { issPage: issPageValues.WELCOME_PAGE } };
router.push = jest.fn();
router.navigate(scenario, currentRoute);
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.POLICY_HOLDER_DETAILS);
});
});
});