Merge pull request #1103 from Safelite/feature/CSR-1112A

Feature/csr 1112 a
This commit is contained in:
Leah Schumann 2023-05-18 08:51:51 -04:00 committed by GitHub
commit 9083fd72f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 170 additions and 237 deletions

View file

@ -19,6 +19,8 @@ module.exports = {
"!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing
"!src/common-components/funnel-header/menu-modal/**/*.vue",
"!src/layouts/schedule/*.vue", // Temp test exclusion while in development
"!src/layouts/schedule/helpers/schedule-helper.js", // Temp test exclusion while in development
"!src/layouts/review/*.vue", // Temp test exclusion while in development
// END
], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],

View file

@ -121,7 +121,7 @@ export default {
isOverflowScrollable: Boolean,
isWide: Boolean,
isCashOrInsurance: Boolean,
modelValue: [Array, Number, String, Object],
modelValue: [Array, Number, String],
value: [Number, String],
validationRules: String,
suppressError: Boolean,
@ -141,7 +141,7 @@ export default {
const fieldOptions = {
value: modelValue,
initialValue: modelValue,
initialValue: null,
};
const { errorMessage, handleBlur, handleChange, meta, validate, errors, resetField } =
@ -255,10 +255,8 @@ export default {
},
},
watch: {
modelValue(newValue) {
this.resetField({
value: newValue,
});
modelValue(newValue, oldValue) {
this.resetField();
},
answers() {
//once we get the answers to display from parent, see if we need a GA event to log what we showed

View file

@ -1,6 +1,6 @@
<template>
<transition name="fade" mode="out-in">
<div class="appointment-type-question" aria-live="polite">
<div class="appointment-type-question" aria-live="polite" v-if="isDisplayed">
<buttonQuestion
ref="buttonQuestion"
customButtonQuestionId="appointmentTypeQuestion"
@ -25,6 +25,7 @@ export default {
modelValue: String,
groupName: String,
isAvailable: Boolean,
isDisplayed: Boolean,
suppressError: Boolean,
validationRules: String,
cmsWidgetName: String,

View file

@ -45,4 +45,18 @@ describe("object-cloning-helper.js", () => {
// Assert
expect(result).toStrictEqual(expected);
});
it("Should return a copy of the array", async () => {
// Arrange
const array = [9, 8, 7, 6, 5, 4, 3, 2, 1];
const expected = [9, 8, 7, 6, 5, 4, 3, 2, 1];
// Act
const result = deepClone(array);
// Assert
expect(result).toStrictEqual(expected);
});
});

View file

@ -1,9 +1,10 @@
import { getPricedMobileFeePart } from "./service-location-helper";
import { getPricedMobileFeePart, getServiceabilityDetails } from "./service-location-helper";
import { storeActions } from "@/constants/store-actions";
const mockStoreActionGetMobileFeePart = storeActions.GET_MOBILE_FEE_PART;
const mockStoreActionPriceOrderItemsAndSaveServerData =
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA;
const mockStoreActionGetServiceabilityDetails = storeActions.GET_SERVICEABILITY_DETAILS;
jest.mock("@/mixins/base-mixin.js", () => ({
methods: {
@ -40,56 +41,87 @@ jest.mock("@/mixins/base-mixin.js", () => ({
},
]);
}
if (actionName === mockStoreActionGetServiceabilityDetails) {
return Promise.resolve({
isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true,
});
}
}),
},
}));
describe("service-location-helper.js", () => {
it("Should return null if no service zip code is passed in", async () => {
// Arrange
const serviceZipCode = null;
const damageType = "Replace";
const parentAccountNumber = 167132;
const billToAccountNumber = 1234;
const expected = null;
describe("getPricedMobileFeePart", () => {
it("Should return null if no service zip code is passed in", async () => {
// Arrange
const serviceZipCode = null;
const damageType = "Replace";
const parentAccountNumber = 167132;
const billToAccountNumber = 1234;
const expected = null;
// Act
const result = await getPricedMobileFeePart(
serviceZipCode,
damageType,
parentAccountNumber,
billToAccountNumber
);
// Act
const result = await getPricedMobileFeePart(
serviceZipCode,
damageType,
parentAccountNumber,
billToAccountNumber
);
// Assert
expect(result).toEqual(expected);
// Assert
expect(result).toEqual(expected);
});
it("Should return the priced mobile fee part", async () => {
// Arrange
const serviceZipCode = "43235";
const damageType = "Replace";
const parentAccountNumber = 167132;
const billToAccountNumber = 1234;
const expected = {
partNumber: "MOBILE FEE",
description: "MOBILE FEE",
partType: "FEE",
laborAmount: 49.99,
sellingPrice: 0,
kitPrice: 0,
};
// Act
const result = await getPricedMobileFeePart(
serviceZipCode,
damageType,
parentAccountNumber,
billToAccountNumber
);
// Assert
expect(result).toEqual(expected);
});
});
it("Should return the priced mobile fee part", async () => {
// Arrange
const serviceZipCode = "43235";
const damageType = "Replace";
const parentAccountNumber = 167132;
const billToAccountNumber = 1234;
describe("getServiceabilityDetails", () => {
it("Should return the serviceability details", async () => {
// Arrange
const serviceZipCode = "43235";
const expected = {
partNumber: "MOBILE FEE",
description: "MOBILE FEE",
partType: "FEE",
laborAmount: 49.99,
sellingPrice: 0,
kitPrice: 0,
};
const expected = {
isGlassServiceableInshop: true,
isRecalibrationServiceableInshop: true,
isGlassServiceableMobile: true,
isRecalibrationServiceableMobile: true,
};
// Act
const result = await getPricedMobileFeePart(
serviceZipCode,
damageType,
parentAccountNumber,
billToAccountNumber
);
// Act
const result = await getServiceabilityDetails(serviceZipCode);
// Assert
expect(result).toEqual(expected);
// Assert
expect(result).toEqual(expected);
});
});
});

View file

@ -236,35 +236,40 @@ export default {
});
},
async setMobileLocation() {
// Validate the Zip Code
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode
);
if (
this.internalModel.addressQuestions.zipCode !==
this.modelValue.addressQuestions.zipCode
) {
// Validate the Zip Code
const zipCodeData = await this.getZipCodeData(
this.internalModel.addressQuestions.zipCode
);
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
} else {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
} else {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
// update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
// update content related to service zip code
this.$emit("updated-mobile-fee-part", mobileFeePart);
this.$emit("updated-serviceability", serviceabilityDetails.data);
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
// Update the page level model
this.$emit("update:modelValue", this.internalModel);
// Update the page level model
this.$emit("update:modelValue", this.internalModel);
if (this.onZipUpdateCallback) {
await this.onZipUpdateCallback(serviceZipCode);
if (this.onZipUpdateCallback) {
await this.onZipUpdateCallback(serviceZipCode);
}
this.closeModal();
}
this.closeModal();
}
},
},

View file

@ -505,36 +505,6 @@ describe("service-location.vue", () => {
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo);
});
test("resets appointment type selection when service zip code is updated by mobile location modal when Mobile is not selected", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
selectedAppointmentType: "Dropoff",
});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const newServiceZipCodeQuestion = {
zipCode: "61606",
state: "IL",
};
// Act
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
// Assert
expect(wrapper.vm.selectedAppointmentType).toStrictEqual(null);
});
test("does not reset appointment type selection when service zip code is updated by mobile location modal when Mobile is selected", async () => {
// Arrange
const { wrapper } = setupMocks({});

View file

@ -47,9 +47,10 @@
alertClass="alert-warning" />
<appointmentTypeQuestion
v-model="selectedAppointmentType"
v-show="!displayNoShopsAlert"
v-show="isAppointmentTypeDisplayed"
:isServiceableMobile="isServiceableMobile"
:isServiceableInshop="isServiceableInshop"
:isDisplayed="isAppointmentTypeDisplayed"
ref="appointmentTypeQuestion"
groupName="appointmentTypeQuestion"
cmsWidgetName="AppointmentTypeQuestionWidget"
@ -71,9 +72,7 @@
<shopQuestion
ref="shopQuestion"
v-show="isShopQuestionDisplayed"
v-model="selectedProviderNumber"
@providerSelected="onProviderSelected"
:serviceZipCode="zipCode"
v-model="selectedProvider"
:selectedAppointmentType="selectedAppointmentType"
:isDisplayed="isShopQuestionDisplayed"
cmsWidgetName="ShopQuestionWidget" />
@ -134,16 +133,6 @@ defineRule("mobile-location-required", (value) => {
return true;
});
const defaultProvider = {
providerNumber: null,
address: {
streetAddress: null,
city: null,
state: null,
zip: null,
},
};
export default {
name: "service-location",
data() {
@ -160,7 +149,6 @@ export default {
isRecalibrationServiceableMobile: null,
selectedAppointmentType: this.getSelectedAppointmentType(),
selectedProvider: this.getSelectedProvider(),
selectedProviderNumber: this.getSelectedProvider().providerNumber,
mobileFeePart: null,
zipContainsMilitaryBase: false,
zipCodeCtu: null,
@ -228,7 +216,7 @@ export default {
if (newValue.zipCode !== this.zipCode) {
this.resetMobileLocation();
this.selectedAppointmentType = null;
this.selectedProvider = defaultProvider;
this.selectedProvider = null;
}
this.state = newValue.state;
@ -263,7 +251,7 @@ export default {
if (!this.selectedAppointmentType == "Mobile") {
this.selectedAppointmentType = null;
}
this.selectedProvider = defaultProvider;
this.selectedProvider = null;
}
},
},
@ -287,8 +275,11 @@ export default {
this.selectedAppointmentType === "Dropoff"
);
},
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
isAppointmentTypeDisplayed() {
return this.zipCode && !this.displayNoShopsAlert;
},
requiresInshopRecalibration() {
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
return (
this.isServiceableInshop &&
this.isGlassServiceableMobile &&
@ -342,6 +333,9 @@ export default {
this.zipContainsMilitaryBase = val;
}
},
setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart;
},
getServiceAddressFromStore() {
return store.getters.order.serviceLocation.address;
},
@ -358,10 +352,7 @@ export default {
return store.getters.order.serviceLocation.appointmentType;
},
getSelectedProvider() {
return store.getters.order.serviceLocation.provider ?? defaultProvider;
},
setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart;
return store.getters.order.serviceLocation.provider;
},
resetMobileLocation() {
this.streetAddress = "";
@ -417,9 +408,6 @@ export default {
async reloadShopData() {
await this.$refs.shopQuestion.reloadShopData(this.zipCode);
},
onProviderSelected(selectedProvider) {
this.selectedProvider = selectedProvider;
},
},
components: {
alert,

View file

@ -434,7 +434,7 @@ describe("shop-question.vue", () => {
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: selectedProvider.providerNumber,
modelValue: selectedProvider,
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
@ -460,18 +460,7 @@ describe("shop-question.vue", () => {
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081",
modelValue: null,
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
},
@ -481,99 +470,22 @@ describe("shop-question.vue", () => {
});
// Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick();
expect(wrapper.vm.answers.length).toEqual(3);
await wrapper.setProps({
selectedAppointmentType: "Inshop",
});
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.answers.length).toEqual(3);
});
// it("Should reload the shops when the service zip code changes", async () => {
// // Arrange
// const { wrapper } = setupMocks({
// mixins: [mockMixin],
// props: {
// modelValue: {
// address: {
// city: "POWELL",
// country: "US",
// state: "OH",
// streetAddress: "3938 POWELL RD",
// zipCode: "43065",
// },
// distanceInMiles: 16.2690495685233,
// providerNumber: "003341",
// },
// serviceZipCode: "43081",
// selectedAppointmentType: "Dropoff",
// cmsWidgetName: cmsWidgetName,
// isDisplayed: true
// },
// mountOptions: {
// attachTo: document.body,
// },
// });
// wrapper.vm.$options.methods.loadInitialData = jest.fn().mockImplementation(() => {
// return new Promise((resolve) => {
// resolve(mockNewShopList);
// });
// });
// // Act
// wrapper.vm.initializeComponent(shopQuestionInitialData);
// await wrapper.vm.$options.watch.serviceZipCode.handler.call(wrapper.vm, "43054");
// // Assert
// expect(wrapper.vm.shopProviders.length).toEqual(3);
// expect(wrapper.vm.shopProviders).toEqual(mockNewShopList.shopProviders);
// });
// it("Should clear the existing answers when the service zip code changes", async () => {
// // Arrange
// const { wrapper } = setupMocks({
// mixins: [mockMixin],
// props: {
// modelValue: {
// address: {
// city: "POWELL",
// country: "US",
// state: "OH",
// streetAddress: "3938 POWELL RD",
// zipCode: "43065",
// },
// distanceInMiles: 16.2690495685233,
// providerNumber: "003341",
// },
// serviceZipCode: "43081",
// selectedAppointmentType: "Dropoff",
// cmsWidgetName: cmsWidgetName,
// },
// mountOptions: {
// attachTo: document.body,
// },
// });
// //Act
// wrapper.vm.initializeComponent(shopQuestionInitialData);
// wrapper.setProps({
// selectedAppointmentType: "Inshop",
// });
// await wrapper.vm.$nextTick();
// // Assert
// expect(wrapper.vm.answers.length).toEqual(3);
// });
});
function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {

View file

@ -68,8 +68,8 @@ export default {
props: {
modelValue: {
type: Object,
default: () => null,
},
serviceZipCode: String,
selectedAppointmentType: String,
cmsWidgetName: String,
validationRules: String,
@ -81,16 +81,12 @@ export default {
},
selectedValue: {
get: function () {
return this.modelValue;
return this.modelValue?.providerNumber;
},
set: function (newValue) {
// Button Question only supports primitive values so we must get the full object to emit to the page
const provider = this.shopProviders?.find(
(provider) => provider.providerNumber == newValue
);
this.$emit("update:modelValue", newValue);
this.$emit("providerSelected", provider);
// Button Question only supports Number, String data types so we must get the full object to emit
const provider = this.getSelectedProviderObject(newValue);
this.$emit("update:modelValue", provider);
},
},
displayDropoffInformation() {
@ -170,11 +166,6 @@ export default {
resetAnswers() {
this.answers = [];
this.shopIndex = 0;
this.selectedValue = "";
if (this.$refs.buttonQuestion) {
this.$refs.buttonQuestion.resetField();
}
},
async reloadShopData(serviceZipCode) {
const result = await this.loadData(serviceZipCode);
@ -185,6 +176,20 @@ export default {
await nextTick();
await this.getNextShopsFromList();
},
getSelectedProviderObject(providerNumber) {
const provider = this.shopProviders?.find(
(provider) => provider.providerNumber == providerNumber
);
return provider;
},
getSelectedProviderIndex(providers, selectedProviderNumber) {
const index = providers.findIndex(
(provider) => provider.providerNumber == selectedProviderNumber
);
return index;
},
},
watch: {
selectedAppointmentType: {
@ -193,6 +198,12 @@ export default {
await this.$nextTick();
this.selectedValue = null;
this.$refs.buttonQuestion?.resetField();
await this.$nextTick();
if (newValue !== "Mobile") {
await this.getNextShopsFromList();
}
@ -200,19 +211,19 @@ export default {
},
shopProviders: {
async handler(newValue) {
//this.resetAnswers();
await this.$nextTick();
if (this.selectedAppointmentType) {
const selectedShopIndex = newValue.findIndex(
(provider) => provider.providerNumber == this.modelValue
const selectedShopIndex = this.getSelectedProviderIndex(
newValue,
this.modelValue?.providerNumber
);
if (selectedShopIndex >= 3) {
await this.getNextShopsFromList(selectedShopIndex + 1);
} else {
await this.getNextShopsFromList();
await this.$nextTick();
}
}
},