Merge branch 'develop' into defect/CSR-1576

This commit is contained in:
Leah Schumann 2023-08-02 13:27:10 -04:00
commit 968a7c7749
18 changed files with 1279 additions and 167 deletions

View file

@ -26,8 +26,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
statements: 75,
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
statements: 80,
},
},
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit

View file

@ -62,6 +62,7 @@ const storeActions = {
SAVE_VEHICLE_MAKE: "saveVehicleMake",
SAVE_VEHICLE_MODEL: "saveVehicleModel",
SAVE_VEHICLE_STYLE: "saveVehicleStyle",
SAVE_VEHICLE: "saveVehicle",
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_ZIP_CODE_INFO: "saveServiceZipCodeInfo",
@ -85,6 +86,7 @@ const storeActions = {
"saveSupportingItemsSuppressingStateResetting",
SAVE_VAPS: "saveVaps",
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
};
export { storeActions };

View file

@ -34,11 +34,13 @@ const storeMutations = {
UPDATE_SERVICE_ZIP: "updateServiceZip",
UPDATE_SERVICE_LOCATION: "updateServiceLocation",
UPDATE_SERVICE_LOCATION_TECH_NOTES: "updateServiceLocationTechNotes",
UPDATE_TECH_NOTES: "updateTechNotes",
UPDATE_SCHEDULE: "updateSchedule",
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
//CUSTOMER MUTATIONS
// CUSTOMER MUTATIONS
UPDATE_CUSTOMER_DETAILS: "updateCustomerDetails",
// ORDER MUTATIONS

View file

@ -16,9 +16,13 @@
:disabled="isDisabled"
:aria-required="isRequired"
:validationRules="validationRules"
:placeHolderText="placeHolderText">
:placeHolderText="placeHolderText"
:valueAsKey="valueAsKey">
<option v-if="placeHolderText" value="" selected>{{ placeHolderText }}</option>
<option v-for="(value, name, index) in options" :value="name" :key="index">
<option
v-for="(value, name, index) in options"
:value="valueAsKey == true ? value : name"
:key="valueAsKey == true ? value : index">
{{ value }}
</option>
</select>
@ -47,6 +51,11 @@ export default {
placeHolderText: String,
cmsWidgetName: String,
hasError: Boolean,
valueAsKey: {
type: Boolean,
required: false,
default: false,
},
},
setup(props) {
const uuid = uuidv4();

View file

@ -54,7 +54,7 @@ export default {
},
},
remainingCount() {
return this.maxLength - this.value.length;
return this.maxLength - (this.value?.length ?? 0);
},
urgentCountdown() {
return this.remainingCount <= this.maxLength * 0.1 ? true : false;

View file

@ -19,15 +19,13 @@
tabindex="-1"
aria-labelledby="footerModalLabel"
aria-hidden="true"
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }"
:style="`height: calc(100% - ${currentFooterAndHeaderHeight}px);`">
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
<div class="menu-modal-container">
<button
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
data-bs-toggle="modal"
data-bs-target="#footerModal"
@click="toggleModal"
aria-label="Hamburger Menu (modal window)">
<div class="bar1"></div>
<div class="bar2"></div>
@ -86,7 +84,6 @@ export default {
data() {
return {
isActive: false,
currentFooterAndHeaderHeight: 0,
};
},
methods: {
@ -104,7 +101,6 @@ export default {
Modal.getInstance(document.getElementById("footerModal")).hide();
},
show() {
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
this.isActive = true;
this.scrollToPageTop();
},

View file

@ -338,12 +338,10 @@ export default {
false
);
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION,
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
address: this.customerQuestions.addressQuestions.streetAddress,
city: this.customerQuestions.addressQuestions.city,
zipCode: this.serviceZipCode,
state: resultMap.serviceZipValidationResponse.state,
zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
},
false

View file

@ -4,11 +4,6 @@ import customerDetails from "@/layouts/customer-details/customer-details.vue";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -36,7 +31,7 @@ describe("customer-details.vue", () => {
await wrapper.vm.forwardButtonAction();
// Assert
expect(navigateToHeritage.navigateToHeritageFunnel).toHaveBeenCalled();
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
});
});
});

View file

@ -32,7 +32,7 @@
<checkboxQuestion
class="mb-5"
cmsWidgetName="TextMeQuestionWidget"
v-model="textMeUpdates" />
v-model="isSmsOptin" />
<textareaQuestion
class="mb-4"
v-model="techNotes"
@ -68,6 +68,7 @@ import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
import { useField, validate } from "vee-validate";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import store from "@/store";
// DEFINE VALIDATION RULES
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
@ -105,12 +106,12 @@ export default {
},
data() {
return {
techNotes: "",
firstName: "",
lastName: "",
emailAddress: "",
phoneNumber: "",
textMeUpdates: null,
firstName: this.getFirstNameFromStore(),
lastName: this.getLastNameFromStore(),
emailAddress: this.getEmailAddressFromStore(),
phoneNumber: this.getPhoneNumberFromStore(),
isSmsOptin: this.getIsSmsOptinFromStore(),
techNotes: this.getTechNotesFromStore(),
};
},
computed: {
@ -122,11 +123,47 @@ export default {
arePagePrerequisitesValid() {
return true;
},
getFirstNameFromStore() {
return store.getters.order.customer.firstName;
},
getLastNameFromStore() {
return store.getters.order.customer.lastName;
},
getEmailAddressFromStore() {
return store.getters.order.customer.emailAddress;
},
getPhoneNumberFromStore() {
return store.getters.order.customer.phoneNumber;
},
getIsSmsOptinFromStore() {
return store.getters.order.customer.isSmsOptin;
},
getTechNotesFromStore() {
return store.getters.order.serviceLocation.techNotes;
},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
this.dispatchStoreAction(
this.storeActions.SAVE_CUSTOMER_DETAILS,
{
firstName: this.firstName,
lastName: this.lastName,
emailAddress: this.emailAddress,
phoneNumber: this.phoneNumber,
isSmsOptin: this.isSmsOptin,
},
false
);
this.dispatchStoreAction(
this.storeActions.SAVE_SERVICE_LOCATION_TECH_NOTES,
this.techNotes,
false
);
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
},
components: {

View file

@ -140,7 +140,9 @@ export default {
arePagePrerequisitesValid() {
return true;
},
backButtonAction() {},
backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
},
forwardButtonAction() {},
editVehicle() {
this.$router.navigateWithoutSaving(

View file

@ -0,0 +1,228 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { applicationConfig } from "@/constants/application-config";
import { storeActions } from "@/constants/store-actions";
import schedule from "@/layouts/schedule/schedule.vue";
import store from "@/store";
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
import { nextTick } from "vue";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
}));
// Mock fetchCmsContentForPage
jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: () => Promise.resolve("content"),
splitCopyOnCMSPlaceHolder: jest.fn(() => ["A", "B"]),
}));
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
lineItems: {
glassParts: [],
supportingItems: [],
},
serviceLocation: {
appointmentType: "Inshop",
},
},
lineItems: {
glassParts: [],
supportingItems: [],
},
};
describe("schedule.vue", () => {
test("Should navigateWithoutSaving", async () => {
//Arrange
const { wrapper } = setupMocks({
customMountOptions: {
router: {
navigateWithoutSaving: jest.fn(),
},
route: { schedule },
},
});
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
//Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
});
test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Mobile",
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(true);
});
test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(true);
});
test("should fail arePagePrerequisitesValid with a replace with no glass parts", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: false,
},
referralNumber: "1234567",
},
payment: {
isInsurance: true,
},
lineItems: {
supportingItems: [],
glassParts: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(false);
});
test("should fail arePagePrerequisitesValid without isInsurance", () => {
//Arrange
const { wrapper } = setupMocks({});
store.getters = {
order: {
schedule: {
date: "2020-01-01",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
appointmentType: "Inshop",
provider: {
providerNumber: "5",
},
},
damage: {
isRepair: true,
},
referralNumber: "1234567",
},
payment: {
isInsurance: null,
},
lineItems: {
supportingItems: [],
glassParts: [],
},
};
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
expect(arePagePrerequisitesValid).toBe(false);
});
});
const mockCmsContent = {};
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
});
mountOptions.global.mocks["$store"] = store;
mountOptions["attachTo"] = document.body;
mountOptions.mixins = [
{
methods: {
getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
return mockCmsContent[widgetName][fieldName];
}),
},
},
];
const wrapper = shallowMount(schedule, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -0,0 +1,712 @@
import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue";
import store from "@/store";
import {
RouteCodeFlags,
PREMIUM_TIME_SLOT_ID_FLAG,
PREMIUM_FEE_PART_TYPE,
} from "@/constants/schedule-constants";
describe("time-slot-modal-list-button-question.vue", () => {
test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => {
//Arrange
const date = "2023-07-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const expectedEmit = [
[
{
date: date,
routeCode: timeSlotId,
startTime: startTime,
endTime: endTime,
jobMaxMinutes: jobMaxMinutes.toString(),
},
],
];
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
},
},
});
wrapper.vm.$refs.timeSlots.closeModal = jest.fn();
wrapper.vm.selectedTimeSlotId = timeSlotId;
//Act
wrapper.vm.setSelectedTimeSlot();
//Assert
expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit);
});
});
describe("time-slot-modal-list-button-question.vue Supplemental information", () => {
test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => {
//Arrange
const mobileCmsWidgetName = "MobileNotPremium";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"]
);
});
test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => {
//Arrange
const mobileCmsWidgetName = "MobilePremium";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" + PREMIUM_TIME_SLOT_ID_FLAG },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[mobileCmsWidgetName]["BodyText"]
);
});
test("No supplemental information should be obtained from CMS - Inshop", async () => {
//Arrange
const inshopWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Inshop",
cmsWidgetName: inshopWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
});
test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: null,
appointmentType: "Dropoff",
cmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
});
test("Correct supplemental information should be retrieved, Dropoff regular", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[dropOffWidgetName]["BodyText"]
);
});
test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"]
);
});
test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.supplementalInformationBlock).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["BodyText"]
);
});
});
describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
test("The correct disclaimer information should be obtained from CMS - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"]
);
});
test("Correct disclaimer information should be retrieved, Dropoff regular", async () => {
//Arrange
const dropOffWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropOffWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[dropOffWidgetName]["FooterText"]
);
});
test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"]
);
});
test("No disclaimer information should be retrieved, non-dropoff", async () => {
//Arrange
const inshopWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "test" },
appointmentType: "Inshop",
cmsWidgetName: inshopWidgetName,
},
},
});
//Assert
expect(wrapper.vm.disclaimerTextBlockCopy).toBeNull();
});
});
describe("time-slot-modal-list-button-question.vue Duration", () => {
test("The correct duration information should be displayed - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Dropoff overnight", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Dropoff", async () => {
//Arrange
const dropoffCmsWidgetName = "Dropoff";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropoffCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[dropoffCmsWidgetName]["SubheaderText"]
);
});
test("Correct duration information should be retrieved, Inshop - convert to hours", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 180,
estimatedServiceMinutesMinimum: 120,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours"
);
});
test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 90,
estimatedServiceMinutesMinimum: 60,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes"
);
});
test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 240,
estimatedServiceMinutesMinimum: 240,
modelValue: { routeCode: "Test" },
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toEqual(
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours"
);
});
test("No duration information should be displayed, Mobile", async () => {
//Arrange
const mobileCmsWidgetName = "Mobile";
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: 240,
estimatedServiceMinutesMinimum: 240,
modelValue: { routeCode: "Test" },
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
expect(wrapper.vm.durationTextBlockCopy).toBeNull();
});
});
describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
test("Available Timeslots should be formatted correctly - Dropoff - Sameday ", async () => {
//Arrange
const sameDayDropOffCmsWidgetName = "Sameday";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId";
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[sameDayDropOffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => {
//Arrange
const overnightDropOffCmsWidgetName = "Overnight";
const date = new Date().toISOString().split("T")[0];
const timeSlotId = "testRouteCodeId" + RouteCodeFlags.OVERNIGHT_DROP_OFF;
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[overnightDropOffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Dropoff", async () => {
//Arrange
const dropoffCmsWidgetName = "Dropoff";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId" + RouteCodeFlags.ALL_DAY_DROP_OFF;
const startTime = "8:00 AM";
const endTime = "8:30 AM";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Dropoff",
dropoffCmsWidgetName: dropoffCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: mockCmsContent[dropoffCmsWidgetName]["HeaderText"],
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Mobile not premium", async () => {
//Arrange
const mobileCmsWidgetName = "MobileNotPremium";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Mobile",
mobileCmsWidgetName: mobileCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
buttonLabel: startTime + " AM - " + endTime + " AM",
value: timeSlotId,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Mobile with premium", async () => {
//Arrange
const mobilePremiumCmsWidgetName = "MobilePremium";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
premiumAppointmentFee: {
partType: PREMIUM_FEE_PART_TYPE,
},
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
offerPremium: true,
},
],
},
appointmentType: "Mobile",
mobilePremiumCmsWidgetName: mobilePremiumCmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = {
additionalButtonData: {
isPremiumAppointment: true,
},
buttonLabel: mockCmsContent[mobilePremiumCmsWidgetName]["HeaderText"],
buttonLabelSubCopy: "+$15.99",
value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG,
};
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
test("Available Timeslots should be formatted correctly - Inshop", async () => {
//Arrange
const cmsWidgetName = "Inshop";
const date = "2020-01-01";
const timeSlotId = "testRouteCodeId";
const startTime = "8:00";
const endTime = "8:30";
const jobMaxMinutes = 100;
const { wrapper } = setupMocks({
customMountOptions: {
propsData: {
estimatedServiceMinutesMaximum: jobMaxMinutes,
dateAndTimeSlotData: {
date: date,
timeSlots: [
{
id: timeSlotId,
startTime: startTime,
endTime: endTime,
},
],
},
appointmentType: "Inshop",
cmsWidgetName: cmsWidgetName,
},
},
});
//Assert
const expectedTimeSlotObject = { buttonLabel: startTime + " AM", value: timeSlotId };
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
});
});
const mockCmsContent = {
MobileNotPremium: {
BodyText: "Mobile, not premium BodyText",
FooterText: "Mobile, not premium FooterText",
SubheaderText: "Mobile, not premium SubheaderText",
HeaderText: "Mobile, not premium HeaderText",
},
MobilePremium: {
"BodyText:": "Mobile, with premium BodyText",
FooterText: "Mobile, with premium FooterText",
SubheaderText: "Mobile, with premium SubheaderText",
HeaderText: "Mobile, with premium HeaderText",
},
Inshop: {
BodyText: "Inshop BodyText",
FooterText: "Inshop FooterText",
SubheaderText: "Inshop SubheaderText",
HeaderText: "Inshop HeaderText",
},
Dropoff: {
BodyText: "Dropoff BodyText",
FooterText: "Dropoff FooterText",
SubheaderText: "Dropoff SubheaderText",
HeaderText: "Dropoff HeaderText",
},
Overnight: {
BodyText: "Dropoff Overnight",
FooterText: "Dropoff, Overnight FooterText",
SubheaderText: "Dropoff, Overnight SubheaderText",
HeaderText: "Dropoff, Overnight HeaderText",
},
Sameday: {
BodyText: "Dropoff Sameday",
FooterText: "Dropoff, Sameday FooterText",
SubheaderText: "Dropoff, Sameday SubheaderText",
HeaderText: "Dropoff, Sameday HeaderText",
},
};
function setupMocks({ customMountOptions }) {
const mountOptions = getMountOptions({
...customMountOptions,
});
mountOptions.global.mocks["$store"] = store;
mountOptions["attachTo"] = document.body;
mountOptions.mixins = [
{
methods: {
getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => {
if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName])
return mockCmsContent[widgetName][fieldName];
}),
getTotalLineItemPrice: jest.fn().mockImplementation(() => 15.99),
},
},
];
const wrapper = shallowMount(timeSlotModalQuestion, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return { wrapper };
}

View file

@ -150,7 +150,7 @@ export default {
return this.modelValue?.routeCode;
},
set: function (newValue) {
// Button Question only supports Number, or String data types so we must get the full object to emit
// Button Question only supports Number, or String data types so we must convert to the full object before emitting
this.selectedValue = this.getSelectedTimeSlotObject(newValue);
},
},
@ -326,7 +326,7 @@ export default {
}
},
modal() {
return this.$refs["timeSlots"];
return this.$refs[this.modalName];
},
},
methods: {

View file

@ -1,9 +1,5 @@
<template>
<dropdownQuestion
:options="values"
disableAutoFill
v-model="selectedValue"
:isDisabled="!values.length" />
<dropdownQuestion disableAutoFill v-model="selectedValue" :valueAsKey="true" />
</template>
<script>
@ -12,16 +8,8 @@ import dropdownQuestion from "@/digital-components/dropdown-question/dropdown-qu
export default {
name: "vehicle-question",
data() {
return {
values: [],
selectedIndex: null,
};
},
props: {
modelValue: String,
updateValues: Function,
},
components: {
@ -30,33 +18,13 @@ export default {
computed: {
selectedValue: {
get() {
return this.selectedIndex?.toString();
get: function () {
return this.modelValue?.toString();
},
set(newValue) {
this.selectedIndex = newValue;
newValue = newValue != null && newValue > -1 ? this.values[newValue] : null;
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
},
methods: {
async getNewValues(selectedYMMS) {
const results = await this.updateValues();
this.values = results?.data;
if (this.values.length == 1) {
this.selectedValue = 0;
} else {
this.selectedValue =
selectedYMMS != null ? this.values.indexOf(selectedYMMS) : null;
}
},
clearValues() {
this.values = [];
this.selectedValue = null;
},
},
};
</script>

View file

@ -15,8 +15,9 @@
ref="vehicleYearQuestion"
class="mb-2 mt-4"
v-model="selectedYear"
:isDisabled="!yearOptions.length"
:options="yearOptions"
cmsWidgetName="VehicleYearQuestion"
:updateValues="updateYearValues"
validationRules="year-required"
placeHolderText="Select year"
inputId="yearQuestionField" />
@ -25,8 +26,9 @@
ref="vehicleMakeQuestion"
class="mb-2 mt-4"
v-model="selectedMake"
:isDisabled="!makeOptions.length"
:options="makeOptions"
cmsWidgetName="VehicleMakeQuestion"
:updateValues="updateMakeValues"
validationRules="make-required"
placeHolderText="Select make"
inputId="makeQuestionField" />
@ -36,7 +38,8 @@
class="mb-2 mt-4"
v-model="selectedModel"
cmsWidgetName="VehicleModelQuestion"
:updateValues="updateModelValues"
:isDisabled="!modelOptions.length"
:options="modelOptions"
validationRules="model-required"
placeHolderText="Select model"
inputId="modelQuestionField" />
@ -46,7 +49,8 @@
class="mb-2 mt-4"
v-model="selectedStyle"
cmsWidgetName="VehicleStyleQuestion"
:updateValues="updateStyleValues"
:isDisabled="!styleOptions.length"
:options="styleOptions"
validationRules="style-required"
placeHolderText="Select style"
inputId="styleQuestionField" />
@ -101,10 +105,14 @@ export default {
name: "vehicle",
data() {
return {
selectedYear: null,
selectedMake: null,
selectedModel: null,
selectedStyle: null,
selectedYear: this.selectedYearfromStore(),
selectedMake: this.selectedMakefromStore(),
selectedModel: this.selectedModelfromStore(),
selectedStyle: this.selectedStylefromStore(),
yearOptions: [],
makeOptions: [],
modelOptions: [],
styleOptions: [],
};
},
@ -113,10 +121,6 @@ export default {
validationRules: String,
},
mounted() {
this.$refs["vehicleYearQuestion"].getNewValues(this.selectedYearfromStore);
},
async beforeRouteEnter(to, from, next) {
// Call APIs
@ -124,6 +128,43 @@ export default {
const experimentForLogging = store.getters.applicationUser.experiments.find(
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
);
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_YEARS,
{}
);
var makeQuestionInitialDataPromise = null;
var modelQuestionInitialDataPromise = null;
var styleQuestionInitialDataPromise = null;
if (
store.getters.order.vehicle.make &&
store.getters.order.vehicle.model &&
store.getters.order.vehicle.style
) {
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MAKES,
{
year: store.getters.order.vehicle.year,
}
);
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MODELS,
{
year: store.getters.order.vehicle.year,
make: store.getters.order.vehicle.make,
}
);
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_STYLES,
{
year: store.getters.order.vehicle.year,
make: store.getters.order.vehicle.make,
model: store.getters.order.vehicle.model,
}
);
}
// If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure.
if (experimentForLogging !== undefined) {
@ -146,6 +187,22 @@ export default {
resultKey: "cmsContent",
promise: cmsContentPromise,
},
{
resultKey: "yearQuestionInitialData",
promise: yearQuestionInitialDataPromise,
},
{
resultKey: "makeQuestionInitialData",
promise: makeQuestionInitialDataPromise,
},
{
resultKey: "modelQuestionInitialData",
promise: modelQuestionInitialDataPromise,
},
{
resultKey: "styleQuestionInitialData",
promise: styleQuestionInitialDataPromise,
},
];
let resultMap = await settleAllPromises(promiseResultMap);
@ -153,48 +210,74 @@ export default {
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.initializeYearComponent(resultMap.yearQuestionInitialData);
if (
makeQuestionInitialDataPromise &&
modelQuestionInitialDataPromise &&
styleQuestionInitialDataPromise
)
vm.initializeMMSComponent(
resultMap.makeQuestionInitialData,
resultMap.modelQuestionInitialData,
resultMap.styleQuestionInitialData
);
});
},
watch: {
selectedYear(year) {
const parsedYear = parseInt(year);
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_YEAR, parsedYear);
async selectedYear(year) {
if (year) {
this.$refs["vehicleMakeQuestion"].getNewValues(this.selectedMakefromStore);
const result = await this.getMakeOptions(year);
this.makeOptions = result?.data;
if (this.selectedYearfromStore() !== year) {
this.selectedMake = null;
this.selectedModel = null;
this.selectedStyle = null;
}
} else {
this.$refs["vehicleMakeQuestion"].clearValues();
this.makeOptions = [];
}
},
selectedMake(make) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false);
async selectedMake(make) {
if (make) {
this.$refs["vehicleModelQuestion"].getNewValues(this.selectedModelfromStore);
const result = await this.getModelOptions(this.selectedYear, make);
this.modelOptions = result?.data;
if (this.selectedMakefromStore() !== make) {
this.selectedModel = null;
this.selectedStyle = null;
}
} else {
this.$refs["vehicleModelQuestion"].clearValues();
this.modelOptions = [];
}
},
selectedModel(model) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MODEL, model, false);
async selectedModel(model) {
if (model) {
this.$refs["vehicleStyleQuestion"].getNewValues(this.selectedStylefromStore);
const result = await this.getStyleOptions(
this.selectedYear,
this.selectedMake,
model
);
this.styleOptions = result?.data;
if (this.selectedModelfromStore() !== model) {
this.selectedStyle = null;
}
} else {
this.$refs["vehicleStyleQuestion"].clearValues();
this.styleOptions = [];
}
},
selectedStyle(style) {
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_STYLE, style, false);
this.setVehicle();
this.setVehicle(this.selectedYear, this.selectedMake, this.selectedModel, style);
},
},
methods: {
setVehicle() {
setVehicle(year, make, model, style) {
return this.dispatchStoreAction(this.storeActions.SET_VEHICLE, {
year: this.$store.getters.vehicle.year,
make: this.$store.getters.vehicle.make,
model: this.$store.getters.vehicle.model,
style: this.$store.getters.vehicle.style,
year: year,
make: make,
model: model,
style: style,
});
},
@ -207,42 +290,29 @@ export default {
},
navigateForward() {
this.dispatchStoreAction(
storeActions.SAVE_VEHICLE,
{
year: this.selectedYear,
make: this.selectedMake,
model: this.selectedModel,
style: this.selectedStyle,
},
false
);
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
},
async updateYearValues() {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_YEARS, {});
initializeYearComponent(initialData) {
this.yearOptions = initialData;
},
async updateMakeValues() {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
year: store.getters.vehicle.year,
});
},
async updateModelValues() {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
year: store.getters.vehicle.year,
make: store.getters.vehicle.make,
});
},
async updateStyleValues() {
return baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
year: store.getters.vehicle.year,
make: store.getters.vehicle.make,
model: store.getters.vehicle.model,
});
},
},
computed: {
displayGeneric() {
return !this.selectedStyle;
initializeMMSComponent(makeOptions, modelOptions, styleOptions) {
this.makeOptions = makeOptions;
this.modelOptions = modelOptions;
this.styleOptions = styleOptions;
},
selectedYearfromStore() {
return store.getters.vehicle.year;
return store.getters.vehicle.year?.toString();
},
selectedMakefromStore() {
return store.getters.vehicle.make;
@ -253,6 +323,32 @@ export default {
selectedStylefromStore() {
return store.getters.vehicle.style;
},
async getMakeOptions(year) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
year: year,
});
},
async getModelOptions(year, make) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
year: year,
make: make,
});
},
async getStyleOptions(year, make, model) {
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
year: year,
make: make,
model: model,
});
},
},
computed: {
displayGeneric() {
return !this.selectedStyle;
},
},
components: {

View file

@ -350,37 +350,22 @@ export default {
// If a VIN has already been found. Validate the Service Zip (in case of changes)
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
// Check if Service Zip entered is serviceable then save the ZIP info and email address
// Check if Service Zip entered is serviceable then save the ZIP info
if (zipCodeData.isServiceable) {
//Only save the service location if the zip changed or we lack zipCodeCtu
//Only save the zipCode, state, and zipCodeCtu if the zip changed or we lack zipCodeCtu
if (
this.$store.getters.order.serviceLocation.zipCode != this.serviceZipCode ||
!this.$store.getters.order.serviceLocation.zipCodeCtu
) {
const vehicleRegistrationInfo = this.$store.getters.vehicle.registration;
if (vehicleRegistrationInfo.zipCode == this.serviceZipCode) {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION,
{
address: vehicleRegistrationInfo.address,
city: vehicleRegistrationInfo.city,
state: vehicleRegistrationInfo.state,
zipCode: vehicleRegistrationInfo.zipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
} else {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
state: zipCodeData.state,
zipCode: this.serviceZipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
}
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
state: zipCodeData.state,
zipCode: this.serviceZipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
);
}
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);

View file

@ -59,9 +59,14 @@ const getDefaultState = () => {
zipCodeCtu: null,
},
},
techNotes: null,
},
customer: {
firstName: null,
lastName: null,
emailAddress: null,
phoneNumber: null,
isSmsOptin: null,
},
damage: {
isRepair: null,
@ -272,6 +277,9 @@ export const mutations = {
},
};
},
updateServiceLocationTechNotes(state, techNotes) {
state.order.serviceLocation.techNotes = techNotes;
},
updateSchedule(state, scheduleInfo) {
if (scheduleInfo) {
state.order.schedule.date = scheduleInfo.date;
@ -281,14 +289,13 @@ export const mutations = {
state.order.schedule.jobMaxMinutes = scheduleInfo.jobMaxMinutes;
}
},
updateCustomerDetails(state, detailsInfo) {
if (detailsInfo) {
state.order.customerDetails.firstName = detailsInfo.firstName;
state.order.customerDetails.lastName = detailsInfo.lastName;
state.order.customerDetails.email = detailsInfo.email;
state.order.customerDetails.telephone = detailsInfo.telephone;
state.order.customerDetails.textUpdates = detailsInfo.textUpdates;
state.order.customerDetails.techNotes = detailsInfo.techNotes;
updateCustomerDetails(state, customerDetails) {
if (customerDetails) {
state.order.customer.firstName = customerDetails.firstName;
state.order.customer.lastName = customerDetails.lastName;
state.order.customer.emailAddress = customerDetails.emailAddress;
state.order.customer.phoneNumber = customerDetails.phoneNumber;
state.order.customer.isSmsOptin = customerDetails.isSmsOptin;
}
},
@ -1619,6 +1626,25 @@ export const actions = {
}
},
saveVehicle(context, { year, make, model, style }) {
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
if (context.state.order.vehicle.year !== year) {
context.commit(storeMutations.UPDATE_YEAR, year);
}
if (context.state.order.vehicle.make !== make) {
context.commit(storeMutations.UPDATE_MAKE, make);
}
if (context.state.order.vehicle.model !== model) {
context.commit(storeMutations.UPDATE_MODEL, model);
}
if (context.state.order.vehicle.style !== style) {
context.commit(storeMutations.UPDATE_STYLE, style);
}
},
saveVehicleDamage(
context,
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }
@ -1947,8 +1973,8 @@ export const actions = {
context.commit(storeMutations.UPDATE_SCHEDULE, scheduleInfo);
},
saveDetails(context, scheduleInfo) {
context.commit(storeMutations.UPDATE_DETAILS, scheduleInfo);
saveCustomerDetails(context, customerDetails) {
context.commit(storeMutations.UPDATE_CUSTOMER_DETAILS, customerDetails);
},
saveServiceZipCodeInfo(context, serviceZipCodeInfo) {
@ -1982,6 +2008,10 @@ export const actions = {
context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo);
},
saveServiceLocationTechNotes(context, techNotesInfo) {
context.commit(storeMutations.UPDATE_SERVICE_LOCATION_TECH_NOTES, techNotesInfo);
},
saveEmail(context, email) {
context.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, email === "" ? null : email);
},

View file

@ -1702,6 +1702,59 @@ describe("Actions", () => {
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null);
});
it("saveVehicle, should save vehicle info", () => {
// Arrange
const context = state;
context.state = {
order: {
vehicle: {
year: "2016",
make: "Toyota",
model: "Accord",
style: "SUV",
},
},
};
const commit = jest.fn();
const dispatch = jest.fn();
context.commit = commit;
context.dispatch = dispatch;
//Act
const payload = {
year: "2015",
make: "Honda",
model: "Civic",
style: "Sedan",
};
actions.saveVehicle(context, payload);
//Assert
expect(dispatch).toHaveBeenNthCalledWith(
1,
storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES
);
expect(dispatch).toHaveBeenNthCalledWith(
2,
storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES
);
if (context.state.order.vehicle.year !== payload.year) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_YEAR, payload.year);
}
if (context.state.order.vehicle.make !== payload.make) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_MAKE, payload.make);
}
if (context.state.order.vehicle.model !== payload.model) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_MODEL, payload.model);
}
if (context.state.order.vehicle.style !== payload.style) {
expect(commit).toBeCalledWith(storeMutations.UPDATE_STYLE, payload.style);
}
});
it("saveVehicleDamage, should wipe out damage if different", () => {
// Arrange
const context = state;