Merge branch 'develop' into CSR-101-vin-lookup

This commit is contained in:
bmauger 2022-04-06 10:04:03 -04:00
commit 9dcc302d8c
65 changed files with 736 additions and 1116 deletions

View file

@ -13,6 +13,8 @@ module.exports = {
"!src/helpers/unit-test-helper.js", "!src/helpers/unit-test-helper.js",
"!src/layouts/component-test/component-test.vue", "!src/layouts/component-test/component-test.vue",
"!src/layouts/form-test/form-test.vue", "!src/layouts/form-test/form-test.vue",
"!src/layouts/license-plate-lookup/license-plate-lookup.vue",
"!src/layouts/vin-lookup/vin-lookup.vue",
"!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue", "!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue",
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
"!src/layouts/address-poc/address-poc.vue", "!src/layouts/address-poc/address-poc.vue",
@ -32,7 +34,8 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: { coverageThreshold: {
global: { global: {
statements: 89, statements: 88,
// 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
}, },
}, },
}; };

View file

@ -16,8 +16,8 @@ describe("funnel-footer.vue", () => {
}); });
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin]
}); });
wrapper.vm.initializeComponent(cmsContent);
// Assert // Assert
const link = wrapper.find("a"); const link = wrapper.find("a");
@ -30,6 +30,7 @@ describe("funnel-footer.vue", () => {
it("Should emit ForwardClicked on button click", async () => { it("Should emit ForwardClicked on button click", async () => {
// Act // Act
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin]
}); });
wrapper.vm.buttonClick(); wrapper.vm.buttonClick();
// Assert // Assert
@ -39,16 +40,28 @@ describe("funnel-footer.vue", () => {
it("Should emit BackClicked on link click", async () => { it("Should emit BackClicked on link click", async () => {
// Act // Act
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin]
}); });
wrapper.vm.linkClick(); wrapper.vm.linkClick();
// Assert // Assert
expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled; expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled;
}); });
it("Should change button text when update button text is called", async () => {
// Act
const wrapper = mount(funnelFooter, {
mixins: [mockMixin]
});
wrapper.vm.updateButtonText('newText');
// Assert
expect(wrapper.componentVM.customButtontext).toBe('newText');
});
}); });
//Mock CMS content const mockMixin = {
const cmsContent = { methods: {
backLink: "text", getCmsContent: jest.fn()
buttonText: "text", }
}; }

View file

@ -18,6 +18,7 @@
<div class="row d-flex flex-row-reverse align-items-center"> <div class="row d-flex flex-row-reverse align-items-center">
<div class="col button-col d-flex" id="stacked"> <div class="col button-col d-flex" id="stacked">
<buttonMain <buttonMain
ref="buttonMain"
isPrimary isPrimary
:buttonText="buttonText" :buttonText="buttonText"
loaderColor="white" loaderColor="white"
@ -48,6 +49,7 @@ export default {
name: "funnelFooter", name: "funnelFooter",
props: { props: {
isDisabled: Boolean, isDisabled: Boolean,
cmsWidgetName: String,
}, },
components: { components: {
textLink, textLink,
@ -56,8 +58,7 @@ export default {
data() { data() {
return { return {
paddingHeight: 0, paddingHeight: 0,
backLink: "", customButtontext: '',
buttonText: "",
} }
}, },
mounted() { mounted() {
@ -69,13 +70,23 @@ export default {
beforeUnmount() { beforeUnmount() {
window.removeEventListener('resize', this.onResize); window.removeEventListener('resize', this.onResize);
}, },
computed: {
backLink(){
return this.getCmsContent(this.cmsWidgetName, 'BackButtonText');
},
buttonText(){
return this.customButtontext ? this.customButtontext : this.getCmsContent(this.cmsWidgetName, 'ForwardButtonText');
}
},
methods: { methods: {
onResize() { onResize() {
this.paddingHeight = document.querySelector(".footer #infoBox").offsetHeight; this.paddingHeight = document.querySelector(".footer #infoBox").offsetHeight;
}, },
initializeComponent(cmsContent) { updateButtonText(newText) {
this.backLink = cmsContent.BackButtonText; this.customButtontext = newText;
this.buttonText = cmsContent.ForwardButtonText; },
removeLoader(){
this.$refs.buttonMain.removeLoader();
}, },
buttonClick() { buttonClick() {
this.$emit("ForwardClicked"); this.$emit("ForwardClicked");

View file

@ -10,8 +10,8 @@ describe("funnelHeader", () => {
setData: { setData: {
imageSrc: "image_url", imageSrc: "image_url",
}, },
mixins: [mockMixin]
}); });
wrapper.vm.initializeComponent(cmsContent);
// Assert // Assert
expect(wrapper.find("img")).toBeTruthy(); expect(wrapper.find("img")).toBeTruthy();
@ -19,7 +19,8 @@ describe("funnelHeader", () => {
}); });
}); });
//Mock CMS content const mockMixin = {
const cmsContent = { methods: {
imageSrc: "image_url", getCmsContent: jest.fn()
}; }
}

View file

@ -24,7 +24,6 @@ export default {
name: "funnel-header", name: "funnel-header",
data() { data() {
return { return {
imageSrc: "",
displayGlobalAlert: false, displayGlobalAlert: false,
globalAlertMessage: { globalAlertMessage: {
isDismissible: false, isDismissible: false,
@ -34,10 +33,13 @@ export default {
}, },
}; };
}, },
methods: { props: {
initializeComponent(cmsContent) { cmsWidgetName: String,
this.imageSrc = cmsContent.LogoImage; },
}, computed: {
imageSrc(){
return this.getCmsContent(this.cmsWidgetName, 'LogoImage');
}
}, },
components: { components: {
alert, alert,

View file

@ -4,19 +4,18 @@ import FunnelSubHeader from "./funnel-sub-header";
describe("FunnelSubHeader.vue", () => { describe("FunnelSubHeader.vue", () => {
it("Should render the 'text' data value as a span value for the header span text value.", async () => { it("Should render the 'text' data value as a span value for the header span text value.", async () => {
// Act // Act
const wrapper = shallowMount(FunnelSubHeader); const wrapper = shallowMount(FunnelSubHeader, {
await wrapper.setData({ mixins: [mockMixin]
text: "FunnelSubHeader Content",
}); });
wrapper.vm.initializeComponent(cmsContent); wrapper.vm.clickEvent();
// Assert // Assert
expect(wrapper.find("h5").text()).toContain("FunnelSubHeader Content"); expect(wrapper.emitted()).toEqual({"click-event": [[]]});
wrapper.unmount();
}); });
}); });
//Mock CMS content const mockMixin = {
const cmsContent = { methods: {
HeaderText: "FunnelSubHeader Content", getCmsContent: jest.fn()
}; }
}

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="current_car_info-text"> <div class="current_car_info-text">
<div class="d-flex align-items-center justify-content-center container-fluid overflow-hidden"> <div class="d-flex align-items-center justify-content-center container-fluid overflow-hidden">
<h5 class="text-center fw-normal mb-0" :class="hasSubText ? 'dark-header' : 'light-header'"> <h5 class="text-center fw-normal mb-0" :class="headerColor">
<span> <span>
{{ text }} {{ text }}
</span> </span>
@ -23,36 +23,33 @@
</template> </template>
<script> <script>
import buttonBack from "@/common-components/button-back/button-back"; import buttonBack from "@/common-components/funnel-sub-header/button-back/button-back";
export default { export default {
name: "FunnelSubHeader", name: "FunnelSubHeader",
data() {
return {
text: "",
subText: "",
};
},
props: { props: {
hasBackButton: Boolean, hasBackButton: Boolean,
backButtonAccessibleText: String backButtonAccessibleText: String,
cmsWidgetName: String,
}, },
components: { components: {
buttonBack, buttonBack,
}, },
computed: { computed: {
hasSubText(){ text(){
return this.subText.length > 0; return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
},
subText(){
return this.getCmsContent(this.cmsWidgetName, 'HeaderSubText');
},
headerColor(){
return this.subText ? 'dark-header' : 'light-header';
} }
}, },
methods: { methods: {
clickEvent() { clickEvent() {
this.$emit("click-event"); this.$emit("click-event");
}, },
initializeComponent(cmsContent) {
this.text = cmsContent.HeaderText;
this.subText = cmsContent.HeaderSubText;
},
}, },
}; };
</script> </script>

View file

@ -51,6 +51,7 @@ export default {
default: '', default: '',
}, },
validationRules: String, validationRules: String,
cmsWidgetName: String,
}, },
setup(props) { setup(props) {
const fieldOptions = { const fieldOptions = {
@ -74,17 +75,10 @@ export default {
meta, meta,
}; };
}, },
data() {
return {
questionText: "",
}
},
methods: {
initializeComponent(cmsContent){
this.questionText = cmsContent;
},
},
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
value: { value: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -15,13 +15,10 @@ jest.mock(
describe("vehicleBanner", () => { describe("vehicleBanner", () => {
test("renders the blurrycar image", async () => { test("renders the blurrycar image", async () => {
// Arrange // Arrange
const { wrapper, cmsContent } = setupMocks({ displayGenericVehicleImageProp: true, imageUrlValue: "NULL" }); const { wrapper } = setupMocks({ displayGenericVehicleImageProp: true, imageUrlValue: "NULL" });
//Act
wrapper.vm.initializeComponent(cmsContent);
// Assert // Assert
expect(wrapper.vm.vehicleImageToDisplay).toEqual( "image_url"); expect(wrapper.vm.vehicleImageToDisplay).toEqual(wrapper.vm.genericVehicleImage);
expect(wrapper.find("img").attributes("class")).toContain("vehicle-image"); expect(wrapper.find("img").attributes("class")).toContain("vehicle-image");
wrapper.unmount(); wrapper.unmount();
}); });
@ -30,10 +27,7 @@ describe("vehicleBanner", () => {
describe("vehicleBanner", () => { describe("vehicleBanner", () => {
test("renders expected vehicle image", async () => { test("renders expected vehicle image", async () => {
// Arrange // Arrange
const { wrapper, cmsContent } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: "url_to_vehicle_image" }); const { wrapper } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: "url_to_vehicle_image" });
//Act
wrapper.vm.initializeComponent(cmsContent);
// Assert // Assert
expect(wrapper.vm.vehicleImageToDisplay).toEqual( "url_to_vehicle_image"); expect(wrapper.vm.vehicleImageToDisplay).toEqual( "url_to_vehicle_image");
@ -45,14 +39,12 @@ describe("vehicleBanner", () => {
describe("vehicleBanner", () => { describe("vehicleBanner", () => {
test("should render car icon when imageUrl is null and category is default", async () => { test("should render car icon when imageUrl is null and category is default", async () => {
// Arrange // Arrange
const { wrapper, cmsContent } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: "NULL" }); const { wrapper } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: "NULL" });
//Act
wrapper.vm.initializeComponent(cmsContent);
var iconUrl = wrapper.vm.vehicleImageToDisplay; var iconUrl = wrapper.vm.vehicleImageToDisplay;
// Assert // Assert
expect(iconUrl).toEqual( "car_icon_url"); expect(iconUrl).toEqual(undefined);
wrapper.unmount(); wrapper.unmount();
}); });
}); });
@ -68,20 +60,10 @@ describe("vehicleBanner", () => {
test.each(params)("renders an icon instead of an image for %s and %s", async (category, expectedIcon) => { test.each(params)("renders an icon instead of an image for %s and %s", async (category, expectedIcon) => {
// Arrange // Arrange
const { wrapper, cmsContent } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: expectedIcon, categoryValue: category }); const { wrapper, cmsContent } = setupMocks({ displayGenericVehicleImageProp: false, imageUrlValue: expectedIcon, categoryValue: category });
//Act //Act
wrapper.vm.initializeComponent(cmsContent);
var vehicleIcon = wrapper.vm.getUnmatchedVehicleIcon(); var vehicleIcon = wrapper.vm.getUnmatchedVehicleIcon();
// Assert // Assert
expect(wrapper.vm.carUnmatchedVehicleIcon).toEqual(cmsContent.CarUnmatchedVehicleIcon);
expect(wrapper.vm.truckUnmatchedVehicleIcon).toEqual(cmsContent.TruckUnmatchedVehicleIcon);
expect(wrapper.vm.vanUnmatchedVehicleIcon).toEqual(cmsContent.VanUnmatchedVehicleIcon);
expect(wrapper.vm.commercialUnmatchedVehicleIcon).toEqual(cmsContent.CommercialVanUnmatchedVehicleIcon);
expect(wrapper.vm.suvUnmatchedVehicleIcon).toEqual(cmsContent.SuvUnmatchedVehicleIcon);
expect(store.getters.vehicle.category).toEqual(category); expect(store.getters.vehicle.category).toEqual(category);
expect(wrapper.vm.vehicleImageToDisplay).toEqual(vehicleIcon);
expect(wrapper.find("img").attributes("class")).toContain("vehicle-image"); expect(wrapper.find("img").attributes("class")).toContain("vehicle-image");
wrapper.unmount(); wrapper.unmount();
}); });
@ -92,6 +74,15 @@ function setupMocks({
imageUrlValue, imageUrlValue,
categoryValue = "CAR" categoryValue = "CAR"
}) { }) {
const mockGetCmsContent = jest.fn();
mockGetCmsContent((cmsWidget, field) => {
return field
});
const mockMixin = {
methods: {
getCmsContent: mockGetCmsContent
}
}
//Mock store //Mock store
store.dispatch = jest.fn(() => {}); store.dispatch = jest.fn(() => {});
store.getters = { vehicle: { category: categoryValue, imageUrl: imageUrlValue } }; store.getters = { vehicle: { category: categoryValue, imageUrl: imageUrlValue } };
@ -104,7 +95,7 @@ function setupMocks({
//Mock props //Mock props
mountOptions.propsData = { displayGenericVehicleImage: displayGenericVehicleImageProp }; mountOptions.propsData = { displayGenericVehicleImage: displayGenericVehicleImageProp };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(vehicleBanner, mountOptions); const wrapper = shallowMount(vehicleBanner, mountOptions);
//Mock CMS content //Mock CMS content

View file

@ -14,18 +14,9 @@ export default {
props: { props: {
displayGenericVehicleImage: { displayGenericVehicleImage: {
type: Boolean, type: Boolean,
required: true required: true,
} },
}, cmsWidgetName: String,
data() {
return {
genericVehicleImage: '',
carUnmatchedVehicleIcon: '',
truckUnmatchedVehicleIcon: '',
vanUnmatchedVehicleIcon: '',
commercialUnmatchedVehicleIcon: '',
suvUnmatchedVehicleIcon: ''
}
}, },
computed: { computed: {
vehicleImageToDisplay(){ vehicleImageToDisplay(){
@ -38,17 +29,27 @@ export default {
} }
return this.$store.getters.vehicle.imageUrl; return this.$store.getters.vehicle.imageUrl;
},
genericVehicleImage(){
return this.getCmsContent(this.cmsWidgetName, 'GenericVehicleImage');
},
carUnmatchedVehicleIcon(){
return this.getCmsContent(this.cmsWidgetName, 'CarUnmatchedVehicleIcon');
},
truckUnmatchedVehicleIcon(){
return this.getCmsContent(this.cmsWidgetName, 'TruckUnmatchedVehicleIcon');
},
vanUnmatchedVehicleIcon(){
return this.getCmsContent(this.cmsWidgetName, 'VanUnmatchedVehicleIcon');
},
commercialUnmatchedVehicleIcon(){
return this.getCmsContent(this.cmsWidgetName, 'CommercialVanUnmatchedVehicleIcon');
},
suvUnmatchedVehicleIcon(){
return this.getCmsContent(this.cmsWidgetName, 'SuvUnmatchedVehicleIcon');
} }
}, },
methods: { methods: {
initializeComponent(cmsContent) {
this.genericVehicleImage = cmsContent.GenericVehicleImage;
this.carUnmatchedVehicleIcon = cmsContent.CarUnmatchedVehicleIcon;
this.truckUnmatchedVehicleIcon = cmsContent.TruckUnmatchedVehicleIcon;
this.vanUnmatchedVehicleIcon = cmsContent.VanUnmatchedVehicleIcon;
this.commercialUnmatchedVehicleIcon = cmsContent.CommercialVanUnmatchedVehicleIcon;
this.suvUnmatchedVehicleIcon = cmsContent.SuvUnmatchedVehicleIcon;
},
getUnmatchedVehicleIcon(){ getUnmatchedVehicleIcon(){
switch(this.$store.getters.vehicle.category){ switch(this.$store.getters.vehicle.category){
case this.vehicleCategories.CAR: case this.vehicleCategories.CAR:

View file

@ -3,7 +3,8 @@ const applicationConfig = {
CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY, CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY, GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30, ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
SAVED_SESSION_TIMEOUT_DAYS: 45 SAVED_SESSION_TIMEOUT_DAYS: 45,
COOKIE_PATH: "/"
}; };
export { applicationConfig }; export { applicationConfig };

View file

@ -43,6 +43,10 @@ const endpoints = {
url: "/vehicle/api/v1/vehicle/Lookup", url: "/vehicle/api/v1/vehicle/Lookup",
method: "POST", method: "POST",
}, },
LookupVinByPlate: {
url: "/vehicle/api/v1/vehicle/lookup-vin-by-plate",
method: "POST",
},
GetPartsOrQuestions: { GetPartsOrQuestions: {
url: "/parts/api/v1/parts/parts-or-questions", url: "/parts/api/v1/parts/parts-or-questions",
method: "POST", method: "POST",
@ -54,7 +58,11 @@ const endpoints = {
LoadOrder: { LoadOrder: {
url: "/order/api/v1/order/load", url: "/order/api/v1/order/load",
method: "POST", method: "POST",
} },
ValidateZip: {
url: "/location/api/v1/location/zip",
method: "GET",
},
}; };
export { endpoints }; export { endpoints };

View file

@ -11,6 +11,7 @@ const errorMessages = {
CITY_REQUIRED: "Please enter your city", CITY_REQUIRED: "Please enter your city",
STATE_REQUIRED: "Please enter your state", STATE_REQUIRED: "Please enter your state",
ZIP_REQUIRED: "Please enter your ZIP", ZIP_REQUIRED: "Please enter your ZIP",
LICENSE_PLATE_REQUIRED: "Please enter your license plate number",
FIRST_NAME_REQUIRED: "Please enter your first name", FIRST_NAME_REQUIRED: "Please enter your first name",
LAST_NAME_REQUIRED: "Please enter your last name", LAST_NAME_REQUIRED: "Please enter your last name",
EMAIL_ADDRESS_REQUIRED: "Please enter your email address", EMAIL_ADDRESS_REQUIRED: "Please enter your email address",

View file

@ -11,11 +11,12 @@ const storeActions = {
GET_EVOX_IMAGE: "getEvoxImage", GET_EVOX_IMAGE: "getEvoxImage",
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms", LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin", LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
SAVE_ORDER: "saveOrder", SAVE_ORDER: "saveOrder",
LOAD_ORDER: "loadOrder", LOAD_ORDER: "loadOrder",
SET_REFERRAL_INFORMATION: "setReferralInformation", SET_REFERRAL_INFORMATION: "setReferralInformation",
SET_LOAD_ORDER_DATA: "setLoadOrderData", VALIDATE_ZIP: "validateZip",
// DEPENDENCY MUTATIONS // DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies", RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies",

View file

@ -10,10 +10,20 @@ const storeMutations = {
UPDATE_VEHICLE_IMAGE_URL: "updateVehicleImageUrl", UPDATE_VEHICLE_IMAGE_URL: "updateVehicleImageUrl",
UPDATE_VEHICLE_IMAGE_VIF_NUMBER: "updateVehicleImageVifNumber", UPDATE_VEHICLE_IMAGE_VIF_NUMBER: "updateVehicleImageVifNumber",
UPDATE_VEHICLE_IMAGE_COLOR: "updateVehicleImageColor", UPDATE_VEHICLE_IMAGE_COLOR: "updateVehicleImageColor",
UPDATE_VEHICLE_VIN: "updateVehicleVin",
UPDATE_IS_REPAIR: "updateIsRepair", UPDATE_IS_REPAIR: "updateIsRepair",
UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips", UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips",
UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace", UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace",
UPDATE_PARTS: "updateParts", UPDATE_PARTS: "updateParts",
UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
UPDATE_REGISTRATION_CITY: "updateRegistrationCity",
UPDATE_REGISTRATION_STATE: "updateRegistrationState",
UPDATE_REGISTRATION_ZIP_CODE: "updateRegistrationZipCode",
UPDATE_REGISTRATION_FIRST_NAME: "updateRegistrationFirstName",
UPDATE_REGISTRATION_LAST_NAME: "updateRegistrationLastName",
UPDATE_SERVICE_LOCATION_ZIP: "updateServiceLocationZip",
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
// ORDER MUTATIONS // ORDER MUTATIONS
UPDATE_REFERRAL_NUMBER: "updateReferralNumber", UPDATE_REFERRAL_NUMBER: "updateReferralNumber",
@ -34,7 +44,7 @@ const storeMutations = {
// OTHER MUTATIONS // OTHER MUTATIONS
UPDATE_PAGE_DATA: "updatePageData", UPDATE_PAGE_DATA: "updatePageData",
SET_LOAD_FUNNEL_SESSION_INFO: "setLoadOrderInformation" UPDATE_STATE_WITH_ORDER_INFORMATION: "updateStateWithOrderInformation"
}; };

View file

@ -1,12 +1,13 @@
import { cookieNames } from "@/constants/cookie-names"; import { cookieNames } from "@/constants/cookie-names";
import store from "@/store"; import store from "@/store";
import { applicationConfig } from "@/constants/application-config";
/* /*
Will update the cookie if present, or create a new one if not. Will update the cookie if present, or create a new one if not.
*/ */
export function updateOrCreateFunnelCookie() { export function updateOrCreateFunnelCookie() {
// Create the cookie // Create the cookie
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}={}; path=/; ${getCookieDomainValue()};`; document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}={}; path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()};`;
// Set up cookie with all the props. // Set up cookie with all the props.
setFunnelCookieProperties({ setFunnelCookieProperties({
@ -18,7 +19,6 @@ export function updateOrCreateFunnelCookie() {
ReferralDate: store.getters.order.referralDate, ReferralDate: store.getters.order.referralDate,
ReferralCorrelationId: store.getters.order.referralCorrelationId, ReferralCorrelationId: store.getters.order.referralCorrelationId,
}); });
} }
/* /*
@ -42,8 +42,7 @@ export function getFunnelCookie() {
Removes cookie from browser. Removes cookie from browser.
*/ */
export function deleteFunnelCookie() { export function deleteFunnelCookie() {
console.log("hi there") document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=; Max-Age=0; path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()}`;
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=; Max-Age=0; path=/; ${getCookieDomainValue()}`;
} }
/* /*
@ -61,7 +60,7 @@ function setFunnelCookieProperties(properties) {
const cookieValueJson = JSON.stringify(cookie); const cookieValueJson = JSON.stringify(cookie);
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${cookieValueJson}; path=/; ${getCookieDomainValue()}`; document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${cookieValueJson}; path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()}`;
} }
} }
} }

View file

@ -46,7 +46,7 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
//return 'vehicle-damage'; //(uncomment) //return 'vehicle-damage'; //(uncomment)
return "vin-lookup"; //This may be temporary return "vin-lookup"; //This may be temporary
} else { } else {
return 'vehicle-damage'; return 'heritage';
//return "estimate" (uncomment) //return "estimate" (uncomment)
} }
} }
@ -58,7 +58,6 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
*/ */
export async function navigateToHeritageFunnel() { export async function navigateToHeritageFunnel() {
// Create the order (or save existing order) when navigating to Heritage Funnel. // Create the order (or save existing order) when navigating to Heritage Funnel.
await saveOrder(); await saveOrder();
@ -67,6 +66,9 @@ export async function navigateToHeritageFunnel() {
{ {
corid: store.getters.order.referralCorrelationId, corid: store.getters.order.referralCorrelationId,
src: "concept-funnel", src: "concept-funnel",
// TODO CSR-28, remove this
cns: "all",
experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=NoShowPackages_CONTROL=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true"
} }
); );
} }

View file

@ -229,7 +229,7 @@ describe("getPageToRouteExistingOrderTo", () => {
//Assert //Assert
//expect(result).toBe('vin-lookup'); //expect(result).toBe('vin-lookup');
expect(result).toBe('vehicle-damage'); expect(result).toBe('heritage');
}); });
test("getPageToRouteExistingOrderTo, should return estimate", async () => { test("getPageToRouteExistingOrderTo, should return estimate", async () => {
@ -285,7 +285,7 @@ describe("getPageToRouteExistingOrderTo", () => {
//Assert //Assert
//expect(result).toBe('estimate'); //expect(result).toBe('estimate');
expect(result).toBe('vehicle-damage'); expect(result).toBe('heritage');
}); });
test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => { test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => {

View file

@ -10,8 +10,7 @@ import baseMixin from "@/mixins/base-mixin";
*/ */
export async function loadOrderIfPresent() { export async function loadOrderIfPresent() {
const funnelCookie = getFunnelCookie(); const funnelCookie = getFunnelCookie();
console.log(funnelCookie)
// Do nothing if there is no cookie or no correlation id. // Do nothing if there is no cookie or no correlation id.
if (funnelCookie == null || funnelCookie.ReferralCorrelationId == null) { if (funnelCookie == null || funnelCookie.ReferralCorrelationId == null) {
return null; return null;
@ -21,7 +20,6 @@ export async function loadOrderIfPresent() {
if (funnelCookie.ShouldResetState) { if (funnelCookie.ShouldResetState) {
baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE); baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE);
deleteFunnelCookie(); deleteFunnelCookie();
console.log("sup")
return null; return null;
} }

View file

@ -4,6 +4,7 @@ import { navigationScenarios } from "@/router/router-constants/navigation-scenar
import { vehicleCategories } from "@/constants/vehicle-categories.js"; import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { cookieNames } from "@/constants/cookie-names"; import { cookieNames } from "@/constants/cookie-names";
import { Form } from "vee-validate";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { getCookieDomainValue } from "@/helpers/heritage-integration/cookie-helper"; import { getCookieDomainValue } from "@/helpers/heritage-integration/cookie-helper";
@ -42,6 +43,7 @@ export function getMountOptions(mockData) {
const global = { const global = {
mocks: mocks, mocks: mocks,
stubs: { Form }
}; };
return { global }; return { global };

View file

@ -1144,7 +1144,7 @@
<script> <script>
import buttonMain from "@/ux-components/button-main/button-main"; import buttonMain from "@/ux-components/button-main/button-main";
import buttonBack from "@/common-components/button-back/button-back"; import buttonBack from "@/common-components/funnel-sub-header/button-back/button-back";
import listCard from "@/ux-components/list-card/list-card"; import listCard from "@/ux-components/list-card/list-card";
import listButton from "@/ux-components/list-button/list-button"; import listButton from "@/ux-components/list-button/list-button";
import alert from "@/ux-components/alert/alert"; import alert from "@/ux-components/alert/alert";

View file

@ -0,0 +1,237 @@
<template>
<Form
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
ref="theForm"
v-slot="{ meta }"
>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall px-5">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="LicensePlateNumber" v-model="licensePlate" inputId="license_plate" disableAutoFill validationRules="license-plate-required" />
</div>
</div>
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="RegistrationZip" v-model="zip" inputId="zip" mask="#####" disableAutoFill validationRules="zip-required" />
</div>
</div>
<div class="row my-2">
<div class="col">
<textboxQuestion cmsWidgetName="EmailAddress" v-model="email" inputId="email" disableAutoFill validationRules="email-address-required|email-address-format" />
</div>
</div>
<alert
class="my-3"
v-if="newServiceZipRequired"
alertClass="alert-danger"
:alertHeadline="noServiceZipWidget.headline"
:alertCopy="noServiceZipWidget.copy"
/>
<alert
class="my-3"
v-if="vinNotValid"
alertClass="alert-danger"
:alertHeadline="noMatchAlertWidget.headline"
:alertCopy="noMatchAlertWidget.copy"
/>
<alert
class="my-3"
v-if="vinDoesNotMatchCarId"
alertClass="alert-warning"
:alertHeadline="matchedDifferentVehicleAlertWidget.headline"
:alertCopy="matchedDifferentVehicleAlertWidget.copy"
/>
<div class="row my-2">
<div class="col">
<textboxQuestion v-if="newServiceZipRequired" cmsWidgetName="ServiceZip" v-model="serviceZip" inputId="serviceZip" disableAutoFill validationRules="zip-required" />
</div>
</div>
<funnelFooter
ref="funnelFooter"
cmsWidgetName="FunnelFooterWidget"
:isDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
</div>
</Form>
</template>
<script>
// Components
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import alert from "@/ux-components/alert/alert";
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { errorMessages } from "@/constants/error-messages";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
// DEFINE VALIDATION RULES
defineRule("license-plate-required", required(errorMessages.LICENSE_PLATE_REQUIRED));
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+).([a-zA-Z]{2,})$/, errorMessages.EMAIL_ADDRESS_FORMAT));
export default {
name: "license-plate-lookup",
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page.
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.noServiceZipWidget = {
headline: resultMap.cmsContent.NoServiceZipWidget.HeadlineText,
copy: resultMap.cmsContent.NoServiceZipWidget.BodyText,
};
vm.noMatchAlertWidget = {
headline: resultMap.cmsContent.NoMatchAlertWidget.HeadlineText,
copy: resultMap.cmsContent.NoMatchAlertWidget.BodyText
};
vm.matchedDifferentVehicleAlertWidget = {
headline: resultMap.cmsContent.MatchedDifferentVehicleAlertWidget.HeadlineText,
copy: resultMap.cmsContent.MatchedDifferentVehicleAlertWidget.BodyText
};
});
},
props: {
validationRules: String,
},
data() {
return {
newServiceZipRequired: false,
vinNotValid: false,
vinDoesNotMatchCarId: false,
licensePlate: '',
zip: '',
email: '',
serviceZip: '',
};
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
resetDependentState() {
store.commit(storeMutations.UPDATE_REGISTRATION_ADDRESS, null);
store.commit(storeMutations.UPDATE_REGISTRATION_CITY, null);
store.commit(storeMutations.UPDATE_REGISTRATION_FIRST_NAME, null);
store.commit(storeMutations.UPDATE_REGISTRATION_LAST_NAME, null);
store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
},
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
async forwardButtonAction() {
const zipValidation = this.serviceZip ? await this.validateZip(this.serviceZip) : await this.validateZip(this.zip);
if (!zipValidation.data.isServiceable) {
this.$refs.funnelFooter.removeLoader();
this.newServiceZipRequired = true;
return;
}
const vinLookup = await this.lookupVin(this.licensePlate, zipValidation.data.state).catch(() => {
this.$refs.funnelFooter.removeLoader();
this.vinNotValid = true;
return;
});
if (vinLookup.data.vehicle.carId !== store.getters.vehicle.carId) {
this.$refs.funnelFooter.updateButtonText(`Continue with ${vinLookup.data.vin} ${vinLookup.data.vehicle.year} ${vinLookup.data.vehicle.make} ${vinLookup.data.vehicle.model}`);
this.$refs.funnelFooter.removeLoader();
this.vinDoesNotMatchCarId = true;
return;
}
const partsData = await baseMixin.methods.dispatchNonBlockingStoreAction(
this.storeActions.GET_PARTS_OR_QUESTIONS,
{
carId: store.getters.vehicle.carId,
glassArray: store.getters.damage.glassToReplace,
zipCode: this.zip,
vin: vinLookup.vin
},
false
);
this.navigateForward(partsData);
},
navigateForward(partsData){
if(partsData.data.partsOrQuestions[0].partQuestions && partsData.data.partsOrQuestions[0].partQuestions.length > 0){
this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_PARTS_QUESTION, this.$route, {}, {}, partsData.data);
return;
} else if((!partsData.data.partsOrQuestions[0].partQuestions || partsData.data.partsOrQuestions[0].partQuestions.length < 1) && partsData.data.partsOrQuestions[0].parts.length > 1) {
this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_PARTS, this.$route, {}, {}, partsData.data);
return;
} else {
this.$router.navigate(this.navigationScenarios.CONTINUING_WITH_SINGLE_PART, this.$route);
}
},
validateZip(zip) {
return baseMixin.methods.dispatchNonBlockingStoreAction(
storeActions.VALIDATE_ZIP,
{ zip }
);
},
lookupVin(plate, state) {
return baseMixin.methods.dispatchNonBlockingStoreAction(
storeActions.LOOKUP_VIN_BY_PLATE,
{ licensePlate: plate, licenseState: state }
);
},
updateStore() {
// if(vehicleDamage){
// store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
// }
store.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
store.commit(storeMutations.UPDATE_YEAR, null);
store.commit(storeMutations.UPDATE_MAKE, null);
store.commit(storeMutations.UPDATE_MODEL, null);
store.commit(storeMutations.UPDATE_STYLE, null);
store.commit(storeMutations.UPDATE_CAR_ID, null);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, null);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, null);
store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, null);
store.commit(storeMutations.UPDATE_REGISTRATION_STATE, null);
store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, null);
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, null);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, null);
},
},
components: {
Form,
funnelHeader,
vehicleBanner,
funnelSubHeader,
textboxQuestion,
alert,
funnelFooter,
},
};
</script>

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall"> <div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
<h1>Reveal Confetti Page Placeholder</h1> <h1>Reveal Confetti Page Placeholder</h1>
<funnelFooter ref="funnelFooter" @back-clicked="backButtonAction" /> <funnelFooter cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" @back-clicked="backButtonAction" />
</div> </div>
</template> </template>
@ -34,12 +34,7 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.funnelFooter.initializeComponent(
resultMap.cmsContent.FunnelFooterWidget
);
}); });
}, },
methods: { methods: {

View file

@ -37,10 +37,10 @@ describe("damage-location-question.vue", () => {
}); });
//Act //Act
damageLocationQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, damageOptions, "car-group"); damageLocationQuestion.methods.initializeComponent.call(wrapper.vm, damageOptions, "car-group");
//Assert //Assert
expect(wrapper.vm.answersToDisplay).toStrictEqual([{ Name: 'Windshield' }, { Name: 'SideDoor' }]) expect(wrapper.vm.damageOptions).toStrictEqual({"backGlassOptions": {"availableReplacementOptions": []}, "driverSideOptions": {"availableReplacementOptions": ["Quarter", "Front"]}, "windshieldOptions": {"availableReplacementOptions": ["Single"]}})
}); });
}); });
@ -64,10 +64,16 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
isMultiSelect: isMultiSelect, isMultiSelect: isMultiSelect,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(damageLocationQuestion, mountOptions); const wrapper = shallowMount(damageLocationQuestion, mountOptions);

View file

@ -26,8 +26,6 @@ export default ({
name: "damageLocationQuestion", name: "damageLocationQuestion",
data(){ data(){
return { return {
questionText: String,
answersFromCms: Array,
damageOptions: Object, damageOptions: Object,
} }
}, },
@ -38,15 +36,20 @@ export default ({
filterByVehicleCategory: Boolean, filterByVehicleCategory: Boolean,
name: String, name: String,
groupName: String, groupName: String,
cmsWidgetName: String,
}, },
methods: { methods: {
initializeComponent(cmsContent, damageOptions){ initializeComponent(damageOptions){
this.questionText = cmsContent.QuestionText;
this.answersFromCms = cmsContent.Answers;
this.damageOptions = damageOptions; this.damageOptions = damageOptions;
}, },
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: { selectedValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -28,10 +28,10 @@ describe("replace-options-question.vue", () => {
const { wrapper, cmsContent, replaceOptions } = setupMocks({ dataFromStoreApi: ["Windshield", "FrontDoor"], filterByVehicleCategory: true}); const { wrapper, cmsContent, replaceOptions } = setupMocks({ dataFromStoreApi: ["Windshield", "FrontDoor"], filterByVehicleCategory: true});
//Act //Act
replaceOptionsQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, replaceOptions, "car-group"); replaceOptionsQuestion.methods.initializeComponent.call(wrapper.vm, replaceOptions, "car-group");
//Assert //Assert
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'Windshield' }, { Name: 'FrontDoor' } ]) expect(wrapper.vm.replaceOptions).toStrictEqual(["Windshield", "FrontDoor"])
}); });
}); });
@ -56,7 +56,7 @@ describe("replace-options-question.vue", () => {
//Act //Act
wrapper.vm.$options.methods.updateSelectedValues.call(wrapper.vm); wrapper.vm.$options.methods.updateSelectedValues.call(wrapper.vm);
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([['Stationary']]); expect(wrapper.vm.selectedValues).toEqual([]);
}); });
}); });
@ -99,12 +99,18 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
isAvailable: isAvailable, isAvailable: isAvailable,
isMultiSelect: isMultiSelect, isMultiSelect: isMultiSelect,
filterByVehicleCategory: filterByVehicleCategory filterByVehicleCategory: filterByVehicleCategory
}; };
mountOptions.mixins = [mockMixin];
//Mock methods //Mock methods
methodsToMock.forEach((methodName) => { methodsToMock.forEach((methodName) => {

View file

@ -1,6 +1,6 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div v-if="isAvailable && this.answersToDisplay.length > 0" class="replace-options-question" :class="this.answersToDisplay.length < 2 ? 'd-none' : ''" aria-live="polite"> <div v-show="isAvailable && this.answersToDisplay.length > 0" class="replace-options-question" :class="this.answersToDisplay.length < 2 ? 'd-none' : ''" aria-live="polite">
<buttonQuestion <buttonQuestion
isWide isWide
:questionText="questionText" :questionText="questionText"
@ -24,9 +24,7 @@ export default ({
name: "replaceOptionsQuestion", name: "replaceOptionsQuestion",
data(){ data(){
return { return {
questionText: String, replaceOptions: [],
answersFromCms: Array,
replaceOptions: Array,
} }
}, },
props: { props: {
@ -37,11 +35,10 @@ export default ({
isMultiSelect: Boolean, isMultiSelect: Boolean,
validationRules: String, validationRules: String,
suppressError: Boolean, suppressError: Boolean,
cmsWidgetName: String,
}, },
methods: { methods: {
initializeComponent(cmsContent, replaceOptions){ initializeComponent(replaceOptions){
this.questionText = cmsContent.QuestionText;
this.answersFromCms = cmsContent.Answers;
this.replaceOptions = replaceOptions; this.replaceOptions = replaceOptions;
}, },
updateSelectedValues() { updateSelectedValues() {
@ -52,6 +49,12 @@ export default ({
}, },
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: { selectedValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -77,7 +77,7 @@ describe("replace-options-question.vue", () => {
sideDoorOptions.methods.initializeComponent.call(wrapper.vm, cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions, "car-group"); sideDoorOptions.methods.initializeComponent.call(wrapper.vm, cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions, "car-group");
//Assert //Assert
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'DriverSide' }, { Name: 'PassengerSide' } ]) expect(wrapper.vm.answersToDisplay).toStrictEqual([])
}); });
}); });
@ -105,11 +105,17 @@ describe("replace-options-question.vue", () => {
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
groupName: groupName, groupName: groupName,
selectedDamageLocations: selectedDamageLocations, selectedDamageLocations: selectedDamageLocations,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(sideDoorOptions, mountOptions); const wrapper = shallowMount(sideDoorOptions, mountOptions);

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="side-door-options"> <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')" aria-live="polite"> <div class="side-doors" v-show="selectedDamageLocations.includes('SideDoor')" aria-live="polite">
<buttonQuestion <buttonQuestion
:questionText="questionText" :questionText="questionText"
isMultiSelect isMultiSelect
@ -15,6 +15,7 @@
</transition> </transition>
<replaceOptionsQuestion <replaceOptionsQuestion
ref="driverSideOptions" ref="driverSideOptions"
cmsWidgetName="DriverSideReplaceOptionsQuestion"
:isAvailable="isDriverSideReplaceOptionsQuestionAvailable" :isAvailable="isDriverSideReplaceOptionsQuestionAvailable"
groupName="driverSideOptions" groupName="driverSideOptions"
isMultiSelect isMultiSelect
@ -24,6 +25,7 @@
/> />
<replaceOptionsQuestion <replaceOptionsQuestion
ref="passengerSideOptions" ref="passengerSideOptions"
cmsWidgetName="PassengerSideReplaceOptionsQuestion"
:isAvailable="isPassengerSideReplaceOptionsQuestionAvailable" :isAvailable="isPassengerSideReplaceOptionsQuestionAvailable"
groupName="passengerSideOptions" groupName="passengerSideOptions"
isMultiSelect isMultiSelect
@ -49,24 +51,16 @@ defineRule("passenger-side-options-required", required(errorMessages.PASSENGER_S
export default ({ export default ({
name: "sideDoorOptions", name: "sideDoorOptions",
data(){
return {
questionText: String,
answersFromCms: Array,
}
},
props: { props: {
groupName: String, groupName: String,
modelValue: Array, modelValue: Array,
selectedDamageLocations: Array, selectedDamageLocations: Array,
cmsWidgetName: String,
}, },
methods: { methods: {
initializeComponent(cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions){ initializeComponent(driverSideOptions, passengerSideOptions){
this.questionText = cmsContent.QuestionText; this.$refs.driverSideOptions.initializeComponent(driverSideOptions);
this.answersFromCms = cmsContent.Answers; this.$refs.passengerSideOptions.initializeComponent(passengerSideOptions);
// Child Components intialize
this.$refs.driverSideOptions.initializeComponent(driverSideReplaceOptions, driverSideOptions);
this.$refs.passengerSideOptions.initializeComponent(passengerSideReplaceOptions, passengerSideOptions);
}, },
getSideDoorReplacementOptions(selectedDoorSides, selectedDriverSideReplaceOptions, selectedPassengerSideReplaceOptions){ getSideDoorReplacementOptions(selectedDoorSides, selectedDriverSideReplaceOptions, selectedPassengerSideReplaceOptions){
return { return {
@ -77,6 +71,12 @@ export default ({
}, },
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: { selectedValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -1,9 +1,5 @@
// Components // Components
import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue"; import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options"; import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question"; import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options"; import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
@ -13,7 +9,7 @@ import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-que
import { settleAllPromises } from "@/helpers/layout-helper.js"; import { settleAllPromises } from "@/helpers/layout-helper.js";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { mount, flushPromises } from "@vue/test-utils"; import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue"; import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
@ -48,90 +44,6 @@ jest.mock("@/store", () => ({
}, },
})); }));
describe("vehicle-damage.vue", () => {
test("Page header is initailized with api data", async (done) => {
//Arrange
const pageHeaderWidgetHeaderText = "Select Damage";
const { wrapper, apiPromise } = setupMocks({
pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
});
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(
pageHeaderWidgetHeaderText
);
done();
});
});
});
describe("vehicle-damage.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
const { wrapper, apiPromise } = setupMocks({
SiteHeaderWidget: SiteHeaderWidget,
});
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(
SiteHeaderWidget
);
done();
});
});
});
describe("vehicle-damage.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
const { wrapper, apiPromise } = setupMocks({
VehicleBannerWidget: VehicleBannerWidget,
});
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(
VehicleBannerWidget
);
done();
});
});
});
describe("vehicle-damage.vue", () => { describe("vehicle-damage.vue", () => {
test("CarId set, arePagePrerequisitesValid should be true ", async () => { test("CarId set, arePagePrerequisitesValid should be true ", async () => {
//Arrange //Arrange
@ -800,7 +712,6 @@ describe("vehicle-damage.vue", () => {
); );
const testNull = validate( "", "replace-options-required"); const testNull = validate( "", "replace-options-required");
const testString = validate( "sldfj", "replace-options-required"); const testString = validate( "sldfj", "replace-options-required");
await flushPromises();
//Assert //Assert
testNull.then(function(data) { testNull.then(function(data) {
@ -863,18 +774,6 @@ function setupMocks({
fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
//Mock damage initialize methods //Mock damage initialize methods
funnelHeader.methods = {
initializeComponent: jest.fn(),
};
vehicleBanner.methods = {
initializeComponent: jest.fn(),
};
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
damageLocationQuestion.methods = { damageLocationQuestion.methods = {
initializeComponent: jest.fn(), initializeComponent: jest.fn(),
}; };
@ -892,22 +791,11 @@ function setupMocks({
updateSelectedValues: jest.fn(), updateSelectedValues: jest.fn(),
}; };
funnelFooter.methods = {
initializeComponent: jest.fn(),
}
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions(mountOptionsMockData);
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
const wrapper = mount(vehicleDamage, mountOptions); const wrapper = shallowMount(vehicleDamage, mountOptions);
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
funnelHeaderWrapper.vm.initializeComponent =
funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent =
vehicleBanner.methods.initializeComponent;
const sideDoorOptionsWrapper = wrapper.findComponent({ name: "sideDoorOptions" }); const sideDoorOptionsWrapper = wrapper.findComponent({ name: "sideDoorOptions" });
sideDoorOptionsWrapper.vm.initializeComponent = sideDoorOptionsWrapper.vm.initializeComponent =
@ -917,21 +805,15 @@ function setupMocks({
windshieldOptionsWrapper.vm.initializeComponent = windshieldOptionsWrapper.vm.initializeComponent =
windshieldOptions.methods.initializeComponent; windshieldOptions.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" });
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
const backGlassOptionsWrapper = wrapper.findComponent({ name: "replaceOptionsQuestion" }); const backGlassOptionsWrapper = wrapper.findComponent({ name: "replaceOptionsQuestion" });
backGlassOptionsWrapper.vm.initializeComponent = backGlassOptionsWrapper.vm.initializeComponent =
replaceOptionsQuestion.methods.initializeComponent; replaceOptionsQuestion.methods.initializeComponent;
const damageLocationQuestionWrapper = wrapper.findComponent({ name: "damageLocationQuestion" }); const damageLocationQuestionWrapper = wrapper.findComponent({ name: "damageLocationQuestion" });
damageLocationQuestionWrapper.vm.initializeComponent = damageLocationQuestionWrapper.vm.initializeComponent =
damageLocationQuestion.methods.initializeComponent; damageLocationQuestion.methods.initializeComponent;
const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" }); wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
funnelFooterWrapper.vm.initializeComponent =
funnelFooter.methods.initializeComponent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -5,12 +5,13 @@
ref="theForm" ref="theForm"
v-slot="{ meta }" v-slot="{ meta }"
> >
<div class="container-fluid shadow rounded-3 px-5 p-2 position-relative make-tall"> <div class="container-fluid shadow rounded-3 p-2 position-relative make-tall px-5">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage=false />
<funnelSubHeader ref="funnelSubHeader" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<damageLocationQuestion <damageLocationQuestion
ref="damageLocation" ref="damageLocation"
cmsWidgetName="DamageLocationQuestion"
v-model="selectedDamageLocations" v-model="selectedDamageLocations"
groupName="DamageLocationQuestion" groupName="DamageLocationQuestion"
/> />
@ -31,6 +32,7 @@
/> />
<sideDoorOptions <sideDoorOptions
ref="sideDoorOptions" ref="sideDoorOptions"
cmsWidgetName="SideDoorSideQuestion"
groupName="SideDoorSideQuestion" groupName="SideDoorSideQuestion"
v-model="sideDoorOptionsData" v-model="sideDoorOptionsData"
v-show="!hasRepairReplaceConflict" v-show="!hasRepairReplaceConflict"
@ -38,13 +40,14 @@
/> />
<replaceOptionsQuestion <replaceOptionsQuestion
ref="backGlassOptions" ref="backGlassOptions"
cmsWidgetName="RearReplaceOptionsQuestion"
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict" :isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
v-model="selectedRearReplaceOptions" v-model="selectedRearReplaceOptions"
groupName="BackGlassReplaceOptionsQuestion" groupName="BackGlassReplaceOptionsQuestion"
validationRules="replace-options-required" validationRules="replace-options-required"
/> />
<funnel-footer <funnel-footer
ref="funnelFooter" cmsWidgetName="FunnelFooterWidget"
:isDisabled="!meta.valid" :isDisabled="!meta.valid"
@back-clicked="backButtonAction" @back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@ -109,30 +112,18 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.funnelSubHeader.initializeComponent(
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.damageLocation.initializeComponent( vm.$refs.damageLocation.initializeComponent(
resultMap.cmsContent.DamageLocationQuestion, resultMap.damageOptions resultMap.damageOptions
); );
vm.$refs.sideDoorOptions.initializeComponent( vm.$refs.sideDoorOptions.initializeComponent(
resultMap.cmsContent.SideDoorSideQuestion, resultMap.cmsContent.DriverSideReplaceOptionsQuestion, resultMap.cmsContent.PassengerSideReplaceOptionsQuestion, resultMap.damageOptions.driverSideOptions.availableReplacementOptions, resultMap.damageOptions.passengerSideOptions.availableReplacementOptions resultMap.damageOptions.driverSideOptions.availableReplacementOptions, resultMap.damageOptions.passengerSideOptions.availableReplacementOptions
); );
vm.$refs.windshieldOptions.initializeComponent( vm.$refs.windshieldOptions.initializeComponent(
resultMap.cmsContent.WindshieldDamageTypeQuestion, resultMap.cmsContent.WindshieldChipCountQuestion, resultMap.damageOptions.windshieldOptions.availableReplacementOptions
resultMap.cmsContent.WindshieldReplaceOptionsQuestion, resultMap.damageOptions.windshieldOptions.availableReplacementOptions
); );
vm.$refs.backGlassOptions.initializeComponent( vm.$refs.backGlassOptions.initializeComponent(
resultMap.cmsContent.RearReplaceOptionsQuestion, resultMap.damageOptions.backGlassOptions.availableReplacementOptions resultMap.damageOptions.backGlassOptions.availableReplacementOptions
);
vm.$refs.funnelFooter.initializeComponent(
resultMap.cmsContent.FunnelFooterWidget
); );
}); });
}, },

View file

@ -1,6 +1,7 @@
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import windshieldChipCountQuestion from "@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question"; import windshieldChipCountQuestion from "@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import store from "@/store"; import store from "@/store";
jest.mock("@/store", () => { return {}; }, {virtual: true}); jest.mock("@/store", () => { return {}; }, {virtual: true});
@ -22,17 +23,17 @@ describe("windshield-chip-count-question.vue", () => {
describe("Windshield-chip-count-question.vue", () => { describe("Windshield-chip-count-question.vue", () => {
test("Should display question and answers from api.", async () => { test("Should display question and answers from api.", async () => {
//Arrange //Arrange
const { wrapper, cmsContent } = setupMocks({modelValueProp: ["One"]}); const { wrapper } = setupMocks({modelValueProp: ["One"]});
//Act //Act
windshieldChipCountQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent); wrapper.setProps({isAvailable: true});
wrapper.vm.updateSelectedValues = jest.fn();
await wrapper.vm.$nextTick();
//Assert //Assert
expect(wrapper.vm.questionText).toStrictEqual("How many chips are we repairing?"); expect(wrapper.vm.updateSelectedValues).toBeCalled();
expect(wrapper.vm.answersFromCms).toStrictEqual([ { Name: 'One' }, { Name: 'Two' }, { Name: 'Three'} ]); });
});
}); });
function setupMocks({ function setupMocks({
@ -54,9 +55,15 @@ describe("windshield-chip-count-question.vue", () => {
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp modelValue: modelValueProp
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions); const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions);

View file

@ -1,6 +1,6 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div class="windshield-chip-count-question" v-if="isAvailable" aria-live="polite"> <div class="windshield-chip-count-question" v-show="isAvailable" aria-live="polite">
<buttonQuestion <buttonQuestion
:questionText="questionText" :questionText="questionText"
:answers="answersFromCms" :answers="answersFromCms"
@ -18,24 +18,15 @@
import buttonQuestion from "@/common-components/button-question/button-question"; import buttonQuestion from "@/common-components/button-question/button-question";
export default ({ export default ({
name: "windshieldOptions", name: "windshieldOptions",
data(){
return {
questionText: String,
answersFromCms: Array
}
},
props: { props: {
modelValue: Array, modelValue: Array,
groupName: String, groupName: String,
isAvailable: Boolean, isAvailable: Boolean,
validationRules: String, validationRules: String,
cmsWidgetName: String,
}, },
methods: { methods: {
initializeComponent(cmsContent){
this.questionText = cmsContent.QuestionText;
this.answersFromCms = cmsContent.Answers;
},
updateSelectedValues() { updateSelectedValues() {
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER // UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1 && this.selectedValues) { if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1 && this.selectedValues) {
@ -44,6 +35,12 @@ export default ({
}, },
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedChipCountValues: { selectedChipCountValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -20,21 +20,6 @@ describe("windshield-damage-type-question.vue", () => {
}); });
}); });
describe("windshield-damage-type-question.vue", () => {
test("Should display question and answers from api.", async () => {
//Arrange
const { wrapper, cmsContent } = setupMocks({modelValueProp: ["Repair"]});
//Act
windshieldDamageTypeQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent);
//Assert
expect(wrapper.vm.questionText).toStrictEqual("What's your windshield damage?");
expect(wrapper.vm.answersFromCms).toStrictEqual([ { Name: 'Repair' }, { Name: 'Replace' } ]);
});
});
function setupMocks({ function setupMocks({
modelValueProp = ["Two"], modelValueProp = ["Two"],
groupName = "WindshieldDamageTypeQuestion", groupName = "WindshieldDamageTypeQuestion",
@ -57,6 +42,12 @@ describe("windshield-damage-type-question.vue", () => {
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp modelValue: modelValueProp
}; };
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(windshieldDamageTypeQuestion, mountOptions); const wrapper = shallowMount(windshieldDamageTypeQuestion, mountOptions);

View file

@ -1,6 +1,6 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div class="windshield-damage-type-question" v-if="isAvailable" aria-live="polite"> <div class="windshield-damage-type-question" v-show="isAvailable" aria-live="polite">
<buttonQuestion <buttonQuestion
:questionText="questionText" :questionText="questionText"
:answers="answersFromCms" :answers="answersFromCms"
@ -20,26 +20,21 @@ import store from "@/store";
export default ({ export default ({
name: "windshieldDamageTypeQuestion", name: "windshieldDamageTypeQuestion",
data(){
return {
questionText: String,
answersFromCms: Array
}
},
props: { props: {
modelValue: Array, modelValue: Array,
groupName: String, groupName: String,
isAvailable: Boolean, isAvailable: Boolean,
suppressError: Boolean, suppressError: Boolean,
validationRules: String, validationRules: String,
}, cmsWidgetName: String,
methods: {
initializeComponent(cmsContent){
this.questionText = cmsContent.QuestionText;
this.answersFromCms = cmsContent.Answers;
},
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: { selectedValues: {
get: function() { get: function() {
return this.modelValue; return this.modelValue;

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="windshield-options"> <div class="windshield-options">
<windshieldDamageTypeQuestion ref="windshieldDamageTypeQuestion" <windshieldDamageTypeQuestion cmsWidgetName="WindshieldDamageTypeQuestion"
:isAvailable=isWindshieldDamageLocation :isAvailable=isWindshieldDamageLocation
:suppressError="hasRepairReplaceConflict || showNoReplacementAvailableError" :suppressError="hasRepairReplaceConflict || showNoReplacementAvailableError"
groupName="WindshieldDamageTypeQuestion" groupName="WindshieldDamageTypeQuestion"
@ -15,13 +15,13 @@
:alertCopy="['We\'re sorry, but we currently offer only repair service for your vehicle type.', 'Need help with next steps? Call us at 800-394-0288.']" :alertCopy="['We\'re sorry, but we currently offer only repair service for your vehicle type.', 'Need help with next steps? Call us at 800-394-0288.']"
:isDismissible="false" :isDismissible="false"
/> />
<windshieldChipCountQuestion ref="windshieldChipCountQuestion" <windshieldChipCountQuestion cmsWidgetName="WindshieldChipCountQuestion"
:isAvailable="isRepairOptionSelected && !hasRepairReplaceConflict" :isAvailable="isRepairOptionSelected && !hasRepairReplaceConflict"
groupName="WindshieldChipCountQuestion" groupName="WindshieldChipCountQuestion"
v-model="selectedWindshieldChipCountValues" v-model="selectedWindshieldChipCountValues"
validationRules="windshield-chip-count-required" validationRules="windshield-chip-count-required"
/> />
<replaceOptionsQuestion ref="replaceOptionsQuestion" <replaceOptionsQuestion ref="replaceOptionsQuestion" cmsWidgetName="WindshieldReplaceOptionsQuestion"
:isAvailable=isReplaceOptionSelected :isAvailable=isReplaceOptionSelected
isMultiSelect isMultiSelect
groupName="WindshieldReplaceOptions" groupName="WindshieldReplaceOptions"
@ -99,13 +99,8 @@ export default ({
}, },
methods: { methods: {
initializeComponent(windshieldDamageTypeQuestionFromCms, windshieldChipCountQuestionFromCms, initializeComponent(
windshieldReplaceOptionsQuestionFromCms, windshieldAvailableReplacementOptions){ windshieldAvailableReplacementOptions){
this.$refs.windshieldDamageTypeQuestion.initializeComponent(windshieldDamageTypeQuestionFromCms);
this.$refs.windshieldChipCountQuestion.initializeComponent(windshieldChipCountQuestionFromCms);
this.$refs.replaceOptionsQuestion.initializeComponent(windshieldReplaceOptionsQuestionFromCms, windshieldAvailableReplacementOptions);
this.windshieldAvailableReplacementOptions = windshieldAvailableReplacementOptions; this.windshieldAvailableReplacementOptions = windshieldAvailableReplacementOptions;
}, },
getWindshieldOptions(selectedWindshieldDamageType, selectedWindshieldChipCount, selectedWindshieldReplaceOptions){ getWindshieldOptions(selectedWindshieldDamageType, selectedWindshieldChipCount, selectedWindshieldReplaceOptions){

View file

@ -27,26 +27,6 @@ describe("make-question.vue", () => {
}); });
}); });
describe("make-question.vue", () => {
test("CMS question text is used as radio question text.", async () => {
//Arrange
const { wrapper, cmsContent } = setupMocks({
cmsQuestionText: "What make is your vehicle?",
});
//Act
makeQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, null);
//Assert
const buttonQuestionComponent = await wrapper.findComponent({
name: "buttonQuestion",
});
expect(buttonQuestionComponent.attributes("questiontext")).toBe(
"What make is your vehicle?"
);
});
});
describe("make-question.vue", () => { describe("make-question.vue", () => {
test("Data from store api are used as radio question answers.", async () => { test("Data from store api are used as radio question answers.", async () => {
//Arrange //Arrange
@ -58,7 +38,6 @@ describe("make-question.vue", () => {
const initialData = makeQuestion.methods.loadInitialData.call(wrapper.vm); const initialData = makeQuestion.methods.loadInitialData.call(wrapper.vm);
makeQuestion.methods.initializeComponent.call( makeQuestion.methods.initializeComponent.call(
wrapper.vm, wrapper.vm,
cmsContent,
initialData initialData
); );
@ -88,9 +67,15 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(makeQuestion, mountOptions); const wrapper = shallowMount(makeQuestion, mountOptions);
//Mock CMS content //Mock CMS content

View file

@ -23,14 +23,17 @@ export default {
name: "make-question", name: "make-question",
data() { data() {
return { return {
questionText: null,
makes: Array, makes: Array,
}; };
}, },
props: { props: {
modelValue: String, modelValue: String,
cmsWidgetName: String,
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValueAsArray: { selectedValueAsArray: {
get: function() { get: function() {
const modelValueAsArray = this.modelValue ? [this.modelValue] : []; const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
@ -52,8 +55,7 @@ export default {
{ year: store.getters.vehicle.year } { year: store.getters.vehicle.year }
); );
}, },
initializeComponent(cmsContent, initialData) { initializeComponent(initialData) {
this.questionText = cmsContent.QuestionText;
this.makes = initialData; this.makes = initialData;
}, },
}, },

View file

@ -6,13 +6,11 @@ import { nextTick } from "vue";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
// Components // Components
import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue"; import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue";
import makeQuestion from "@/layouts/vehicle-make/make-question/make-question"; import makeQuestion from "@/layouts/vehicle-make/make-question/make-question";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import store from "@/store"; import store from "@/store";
jest.mock("@/store", () => ({ jest.mock("@/store", () => ({
@ -39,12 +37,8 @@ jest.mock("@/helpers/layout-helper.js", () => ({
describe("vehicle-make.vue", () => { describe("vehicle-make.vue", () => {
test("Make question component is initized with api data", async (done) => { test("Make question component is initized with api data", async (done) => {
//Arrange //Arrange
const vehicleMakeQuestionCmsContent = {
QuestionText: "What make is your vehicle?",
};
const makeQuestionInitialData = ["honda", "ford", "dodge"]; const makeQuestionInitialData = ["honda", "ford", "dodge"];
const { wrapper, apiPromise } = setupMocks({ const { wrapper, apiPromise } = setupMocks({
vehicleMakeQuestionCmsContent: vehicleMakeQuestionCmsContent,
makeQuestionInitialData: makeQuestionInitialData, makeQuestionInitialData: makeQuestionInitialData,
}); });
@ -59,7 +53,6 @@ describe("vehicle-make.vue", () => {
//Assert //Assert
apiPromise.finally(() => { apiPromise.finally(() => {
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith( expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(
vehicleMakeQuestionCmsContent,
makeQuestionInitialData makeQuestionInitialData
); );
done(); done();
@ -67,90 +60,6 @@ describe("vehicle-make.vue", () => {
}); });
}); });
describe("vehicle-make.vue", () => {
test("Page header is initailized with api data", async (done) => {
//Arrange
const pageHeaderWidgetHeaderText = "Select a make to get started";
const { wrapper, apiPromise } = setupMocks({
pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
});
//Act
vehicleMake.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-make" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(
pageHeaderWidgetHeaderText
);
done();
});
});
});
describe("vehicle-make.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
const { wrapper, apiPromise } = setupMocks({
SiteHeaderWidget: SiteHeaderWidget,
});
//Act
vehicleMake.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-make" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(
SiteHeaderWidget
);
done();
});
});
});
describe("vehicle-make.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
const { wrapper, apiPromise } = setupMocks({
VehicleBannerWidget: VehicleBannerWidget,
});
//Act
vehicleMake.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-make" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(
VehicleBannerWidget
);
done();
});
});
});
describe("vehicle-make.vue", () => { describe("vehicle-make.vue", () => {
test("BackButtonAction triggers a router.navigate change", async (done) => { test("BackButtonAction triggers a router.navigate change", async (done) => {
//Arrange //Arrange
@ -262,32 +171,13 @@ function setupMocks({
loadInitialData: jest.fn(), loadInitialData: jest.fn(),
initializeComponent: jest.fn(), initializeComponent: jest.fn(),
}; };
funnelHeader.methods = {
initializeComponent: jest.fn(),
};
vehicleBanner.methods = {
initializeComponent: jest.fn(),
};
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleMake, mountOptions); const wrapper = shallowMount(vehicleMake, mountOptions);
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" }); const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
makeQuestionWrapper.vm.initializeComponent = makeQuestionWrapper.vm.initializeComponent =
makeQuestion.methods.initializeComponent; makeQuestion.methods.initializeComponent;
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" }); wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
funnelHeaderWrapper.vm.initializeComponent =
funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent =
vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({
name: "funnelSubHeader",
});
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,16 +1,16 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-0 position-relative make-tall"> <div class="container-fluid shadow rounded-3 p-0 position-relative make-tall">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<div class="select-car"> <div class="select-car">
<div class="select-car-form rounded text-center"> <div class="select-car-form rounded text-center">
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=true /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
<funnelSubHeader <funnelSubHeader
ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Year" backButtonAccessibleText="Change Vehicle Year"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<makeQuestion v-model="selectedMake" ref="makeQuestion" /> <makeQuestion v-model="selectedMake" ref="makeQuestion" cmsWidgetName="VehicleMakeQuestion" />
</div> </div>
</div> </div>
</div> </div>
@ -59,17 +59,8 @@ export default {
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.makeQuestion.initializeComponent( vm.$refs.makeQuestion.initializeComponent(
resultMap.cmsContent.VehicleMakeQuestion,
resultMap.makeQuestionInitialData resultMap.makeQuestionInitialData
); );
}); });

View file

@ -27,29 +27,6 @@ describe("model-question.vue", () => {
}); });
}); });
describe("model-question.vue", () => {
test("CMS question text is used as radio question text.", async () => {
//Arrange
const { wrapper, cmsContent } = setupMocks({
cmsQuestionText: "What model is your vehicle?",
});
//Act
modelQuestion.methods.initializeComponent.call(
wrapper.vm,
cmsContent,
null
);
//Assert
const buttonQuestionComponent = await wrapper.findComponent({
name: "buttonQuestion",
});
expect(buttonQuestionComponent.attributes("questiontext")).toBe(
"What model is your vehicle?"
);
});
});
describe("model-question.vue", () => { describe("model-question.vue", () => {
test("Data from store api are used as radio question answers.", async () => { test("Data from store api are used as radio question answers.", async () => {
@ -62,7 +39,6 @@ describe("model-question.vue", () => {
const initialData = modelQuestion.methods.loadInitialData.call(wrapper.vm); const initialData = modelQuestion.methods.loadInitialData.call(wrapper.vm);
modelQuestion.methods.initializeComponent.call( modelQuestion.methods.initializeComponent.call(
wrapper.vm, wrapper.vm,
cmsContent,
initialData initialData
); );
@ -92,9 +68,15 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(modelQuestion, mountOptions); const wrapper = shallowMount(modelQuestion, mountOptions);
//Mock CMS content //Mock CMS content

View file

@ -23,14 +23,17 @@ export default {
name: "model-question", name: "model-question",
data() { data() {
return { return {
questionText: null,
models: Array, models: Array,
}; };
}, },
props: { props: {
modelValue: String, modelValue: String,
cmsWidgetName: String,
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValueAsArray: { selectedValueAsArray: {
get: function() { get: function() {
const modelValueAsArray = this.modelValue ? [this.modelValue] : []; const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
@ -52,8 +55,7 @@ export default {
{ year: store.getters.vehicle.year, make: store.getters.vehicle.make } { year: store.getters.vehicle.year, make: store.getters.vehicle.make }
); );
}, },
initializeComponent(cmsContent, initialData) { initializeComponent(initialData) {
this.questionText = cmsContent.QuestionText;
this.models = initialData; this.models = initialData;
}, },
}, },

View file

@ -1,9 +1,6 @@
// Components // Components
import vehicleModel from "@/layouts/vehicle-model/vehicle-model.vue"; import vehicleModel from "@/layouts/vehicle-model/vehicle-model.vue";
import modelQuestion from "@/layouts/vehicle-model/model-question/model-question"; import modelQuestion from "@/layouts/vehicle-model/model-question/model-question";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
// Supporting files // Supporting files
import { settleAllPromises } from "@/helpers/layout-helper.js"; import { settleAllPromises } from "@/helpers/layout-helper.js";
@ -13,6 +10,7 @@ import { nextTick } from "vue";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store"; import store from "@/store";
// Mock our module for promises. // Mock our module for promises.
@ -39,12 +37,8 @@ jest.mock("@/store", () => ({
describe("vehicle-model.vue", () => { describe("vehicle-model.vue", () => {
test("Model question component is initized with api data", async (done) => { test("Model question component is initized with api data", async (done) => {
//Arange //Arange
const buttonQuestionContent = {
QuestionText: "What model is your vehicle?",
};
const modelQuestionInitialData = ["accord", "civic", "insight"]; const modelQuestionInitialData = ["accord", "civic", "insight"];
const { wrapper, apiPromise } = setupMocks({ const { wrapper, apiPromise } = setupMocks({
buttonQuestionContent: buttonQuestionContent,
modelQuestionInitialData: modelQuestionInitialData, modelQuestionInitialData: modelQuestionInitialData,
}); });
//Act //Act
@ -57,88 +51,13 @@ describe("vehicle-model.vue", () => {
//Assert //Assert
apiPromise.finally(() => { apiPromise.finally(() => {
expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith( expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith(
buttonQuestionContent,
modelQuestionInitialData modelQuestionInitialData
); );
done(); done();
}); });
}); });
}); });
describe("vehicle-model.vue", () => {
test("Page header is initailized with api data", async (done) => {
//Arrange
const pageHeaderWidgetHeaderText = "Select a model to get started";
const { wrapper, apiPromise } = setupMocks({
pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
});
//Act
vehicleModel.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-model" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(
pageHeaderWidgetHeaderText
);
done();
});
});
});
describe("vehicle-model.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
const { wrapper, apiPromise } = setupMocks({
SiteHeaderWidget: SiteHeaderWidget,
});
//Act
vehicleModel.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-model" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(
SiteHeaderWidget
);
done();
});
});
});
describe("vehicle-model.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
const { wrapper, apiPromise } = setupMocks({
VehicleBannerWidget: VehicleBannerWidget,
});
//Act
vehicleModel.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-model" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(
VehicleBannerWidget
);
done();
});
});
});
describe("vehicle-model.vue", () => { describe("vehicle-model.vue", () => {
test("BackButtonAction triggers a router.navigate change", async (done) => { test("BackButtonAction triggers a router.navigate change", async (done) => {
//Arrange //Arrange
@ -246,35 +165,13 @@ function setupMocks({
loadInitialData: jest.fn(), loadInitialData: jest.fn(),
initializeComponent: jest.fn(), initializeComponent: jest.fn(),
}; };
funnelHeader.methods = {
initializeComponent: jest.fn(),
};
vehicleBanner.methods = {
initializeComponent: jest.fn(),
};
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleModel, mountOptions); const wrapper = shallowMount(vehicleModel, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
const modelQuestionWrapper = wrapper.findComponent({ name: "modelQuestion" }); const modelQuestionWrapper = wrapper.findComponent({ name: "modelQuestion" });
modelQuestionWrapper.vm.initializeComponent = modelQuestionWrapper.vm.initializeComponent =
modelQuestion.methods.initializeComponent; modelQuestion.methods.initializeComponent;
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
funnelHeaderWrapper.vm.initializeComponent =
funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent =
vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({
name: "funnelSubHeader",
});
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,16 +1,16 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-0 position-relative"> <div class="container-fluid shadow rounded-3 p-0 position-relative">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<div class="select-car"> <div class="select-car">
<div class="select-car-form rounded text-center"> <div class="select-car-form rounded text-center">
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=true /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
<funnelSubHeader <funnelSubHeader
ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Make" backButtonAccessibleText="Change Vehicle Make"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<modelQuestion v-model="selectedModel" ref="modelQuestion" /> <modelQuestion v-model="selectedModel" ref="modelQuestion" cmsWidgetName="VehicleModelQuestion" />
</div> </div>
</div> </div>
</div> </div>
@ -60,17 +60,8 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.modelQuestion.initializeComponent( vm.$refs.modelQuestion.initializeComponent(
resultMap.cmsContent.VehicleModelQuestion,
resultMap.modelQuestionInitialData resultMap.modelQuestionInitialData
); );
}); });

View file

@ -1,9 +1,5 @@
// Components // Components
import vehicleParts from "@/layouts/vehicle-parts/vehicle-parts.vue"; import vehicleParts from "@/layouts/vehicle-parts/vehicle-parts.vue";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass-part-question"; import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass-part-question";
// Supporting Files // Supporting Files
@ -12,6 +8,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue"; import { nextTick } from "vue";
import baseMixin from "@/mixins/base-mixin.js";
import store from "@/store"; import store from "@/store";
// Mock our module for promises. // Mock our module for promises.
@ -65,101 +62,13 @@ const basePartResponse = {
describe("vehicle-parts.vue", () => { describe("vehicle-parts.vue", () => {
test("Page header is initialized with api data", async (done) => { test("Set cms content called on load", async (done) => {
//Arrange //Arrange
store.getters.pageData.mockReturnValueOnce(basePartResponse);
store.getters.lineItems = { glassParts: {} }
const pageHeaderWidgetHeaderText = "Select Parts";
const { wrapper, apiPromise } = setupMocks(
{
pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
mountOptionsMockData: {
router: {
navigateAfterSave: jest.fn()
},
route: {
query: {
fmgPage: 'vehicle-parts',
}
},
store: {
getters: store.getters
},
}
});
//Act
vehicleParts.beforeRouteEnter.call(wrapper.vm,
{ query: { fmgPage: "vehicle-parts" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(pageHeaderWidgetHeaderText);
done();
});
});
test("Page logo image is initialized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
store.getters.pageData.mockReturnValueOnce(basePartResponse);
store.getters.lineItems = { glassParts: {} }
const { wrapper, apiPromise } = setupMocks(
{
SiteHeaderWidget: SiteHeaderWidget,
mountOptionsMockData: {
router: {
navigateAfterSave: jest.fn()
},
route: {
query: {
fmgPage: 'vehicle-parts',
}
},
store: {
getters: store.getters
},
}
});
//Act
vehicleParts.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-parts" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(SiteHeaderWidget);
done();
});
});
test("Vehicle image is initialized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
store.getters.pageData.mockReturnValue(basePartResponse); store.getters.pageData.mockReturnValue(basePartResponse);
store.getters.lineItems = { glassParts: {} } store.getters.lineItems = { glassParts: {} }
const { wrapper, apiPromise } = setupMocks( const { wrapper, apiPromise } = setupMocks(
{ {
VehicleBannerWidget: VehicleBannerWidget,
mountOptionsMockData: { mountOptionsMockData: {
router: { router: {
navigateAfterSave: jest.fn() navigateAfterSave: jest.fn()
@ -182,10 +91,11 @@ describe("vehicle-parts.vue", () => {
undefined, undefined,
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.setCmsContent = jest.fn();
//Assert //Assert
apiPromise.finally(() => { apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(VehicleBannerWidget); expect(wrapper.vm.setCmsContent).toHaveBeenCalled();
done(); done();
}); });
}); });
@ -375,29 +285,13 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
settleAllPromises.mockImplementation(() => apiPromise); settleAllPromises.mockImplementation(() => apiPromise);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
//Mock damage initialize methods
funnelHeader.methods = { initializeComponent: jest.fn() };
vehicleBanner.methods = { initializeComponent: jest.fn() };
funnelSubHeader.methods = { initializeComponent: jest.fn() };
funnelFooter.methods = { initializeComponent: jest.fn() }
const mountOptions = getMountOptions(mountOptionsMockData,); const mountOptions = getMountOptions(mountOptionsMockData,);
const wrapper = shallowMount(vehicleParts, mountOptions); const wrapper = shallowMount(vehicleParts, mountOptions);
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
funnelHeaderWrapper.vm.initializeComponent = funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent = vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader", });
funnelSubHeaderWrapper.vm.initializeComponent = funnelSubHeader.methods.initializeComponent;
const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter", });
funnelFooterWrapper.vm.initializeComponent = funnelFooter.methods.initializeComponent;
const partQuestionRearWrapper = wrapper.findComponent({ name: "glassPartQuestion", }); const partQuestionRearWrapper = wrapper.findComponent({ name: "glassPartQuestion", });
partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent; partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent;
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall"> <div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
<funnelHeader ref="funnelHeader" /> <funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage="false" /> <vehicleBanner ref="vehicleBanner" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
<funnelSubHeader ref="funnelSubHeader" /> <funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
<div class="container-fluid prevent-squish my-5"> <div class="container-fluid prevent-squish my-5">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -31,12 +31,8 @@
:colorAnswers="item.colorAnswers" :colorAnswers="item.colorAnswers"
/> />
</div> </div>
<funnelFooter <funnelFooter cmsWidgetName="FunnelFooterWidget" ref="funnelFooter" @back-clicked="backButtonAction" @ForwardClicked="forwardButtonAction" />
ref="funnelFooter" </div>
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction"
/>
</div>
</template> </template>
<script> <script>
@ -55,43 +51,29 @@ import { storeMutations } from "@/constants/store-mutations";
import store from "@/store"; import store from "@/store";
export default { export default {
name: "vehicle-parts", name: "vehicle-parts",
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [{
{ resultKey: "cmsContent",
resultKey: "cmsContent", promise: cmsContentPromise,
promise: cmsContentPromise, }, ];
}, const resultMap = await settleAllPromises(promiseResultMap);
]; // Call the "next" function to complete the transition to this page.
const resultMap = await settleAllPromises(promiseResultMap); next((vm) => {
// Call the "next" function to complete the transition to this page. vm.setCmsContent(resultMap.cmsContent);
next((vm) => {
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.funnelSubHeader.initializeComponent(
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelFooter.initializeComponent(
resultMap.cmsContent.FunnelFooterWidget
);
// Glass Part Question dynamic component // Glass Part Question dynamic component
Object.keys(vm.$refs)
Object.keys(vm.$refs) .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) .forEach((c) =>
.forEach((c) => vm.$refs[c][0].initializeComponent({
vm.$refs[c][0].initializeComponent({ ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget, FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget, })
}) );
);
// Set alertData for the page alert. These use props so we don't call // Set alertData for the page alert. These use props so we don't call
// initializeComponent here. // initializeComponent here.
@ -154,7 +136,6 @@ export default {
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
// Check if isRepair is populated and if the pageData we need is here (Parts data) // Check if isRepair is populated and if the pageData we need is here (Parts data)
console.log(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))
if ( if (
(store.getters.damage.isRepair != null) && (store.getters.damage.isRepair != null) &&
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)) Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS))

View file

@ -27,30 +27,6 @@ describe("style-question.vue", () => {
}); });
}); });
describe("style-question.vue", () => {
test("CMS question text is used as radio question text.", async () => {
//Arrange
const { wrapper, cmsContent } = setupMocks({
cmsQuestionText: "What style is your vehicle?",
});
//Act
styleQuestion.methods.initializeComponent.call(
wrapper.vm,
cmsContent,
null
);
//Assert
const buttonQuestionComponent = await wrapper.findComponent({
name: "buttonQuestion",
});
expect(buttonQuestionComponent.attributes("questiontext")).toBe(
"What style is your vehicle?"
);
});
});
describe("style-question.vue", () => { describe("style-question.vue", () => {
test("Data from store api are used as radio question answers.", async () => { test("Data from store api are used as radio question answers.", async () => {
//Arrange //Arrange
@ -62,7 +38,6 @@ describe("style-question.vue", () => {
const initialData = styleQuestion.methods.loadInitialData.call(wrapper.vm); const initialData = styleQuestion.methods.loadInitialData.call(wrapper.vm);
styleQuestion.methods.initializeComponent.call( styleQuestion.methods.initializeComponent.call(
wrapper.vm, wrapper.vm,
cmsContent,
initialData initialData
); );
@ -90,9 +65,15 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(styleQuestion, mountOptions); const wrapper = shallowMount(styleQuestion, mountOptions);
//Mock CMS content //Mock CMS content

View file

@ -23,14 +23,17 @@ export default {
name: "style-question", name: "style-question",
data() { data() {
return { return {
questionText: null,
styles: Array, styles: Array,
}; };
}, },
props: { props: {
modelValue: String, modelValue: String,
cmsWidgetName: String,
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValueAsArray: { selectedValueAsArray: {
get: function() { get: function() {
const modelValueAsArray = this.modelValue ? [this.modelValue] : []; const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
@ -56,8 +59,7 @@ export default {
} }
); );
}, },
initializeComponent(cmsContent, initialData) { initializeComponent(initialData) {
this.questionText = cmsContent.QuestionText;
this.styles = initialData; this.styles = initialData;
}, },
}, },

View file

@ -1,8 +1,5 @@
// Components // Components
import vehicleStyle from "@/layouts/vehicle-style/vehicle-style.vue"; import vehicleStyle from "@/layouts/vehicle-style/vehicle-style.vue";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import styleQuestion from "@/layouts/vehicle-style/style-question/style-question"; import styleQuestion from "@/layouts/vehicle-style/style-question/style-question";
// Supporting files // Supporting files
@ -12,6 +9,7 @@ import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
// Mock our module for promises. // Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({ jest.mock("@/helpers/layout-helper.js", () => ({
@ -35,12 +33,8 @@ jest.mock("@/store", () => ({
describe("vehicle-style.vue", () => { describe("vehicle-style.vue", () => {
test("Style question component is initized with api data", async (done) => { test("Style question component is initized with api data", async (done) => {
//Arrange //Arrange
const vehicleStyleQuestionCmsContent = {
QuestionText: "What style is your vehicle?",
};
const styleQuestionInitialData = ["2 Door", "4 Door"]; const styleQuestionInitialData = ["2 Door", "4 Door"];
const { wrapper, apiPromise } = setupMocks({ const { wrapper, apiPromise } = setupMocks({
vehicleStyleQuestionCmsContent: vehicleStyleQuestionCmsContent,
styleQuestionInitialData: styleQuestionInitialData, styleQuestionInitialData: styleQuestionInitialData,
}); });
@ -55,7 +49,6 @@ describe("vehicle-style.vue", () => {
//Assert //Assert
apiPromise.finally(() => { apiPromise.finally(() => {
expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith( expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith(
vehicleStyleQuestionCmsContent,
styleQuestionInitialData styleQuestionInitialData
); );
done(); done();
@ -63,90 +56,6 @@ describe("vehicle-style.vue", () => {
}); });
}); });
describe("vehicle-style.vue", () => {
test("Page header is initailized with api data", async (done) => {
//Arrange
const pageHeaderWidgetHeaderText = "Select a style to get started";
const { wrapper, apiPromise } = setupMocks({
pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
});
//Act
vehicleStyle.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-style" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(
pageHeaderWidgetHeaderText
);
done();
});
});
});
describe("vehicle-style.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
const { wrapper, apiPromise } = setupMocks({
SiteHeaderWidget: SiteHeaderWidget,
});
//Act
vehicleStyle.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-style" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(
SiteHeaderWidget
);
done();
});
});
});
describe("vehicle-style.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
const { wrapper, apiPromise } = setupMocks({
VehicleBannerWidget: VehicleBannerWidget,
});
//Act
vehicleStyle.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-style" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(
VehicleBannerWidget
);
done();
});
});
});
describe("vehicle-style.vue", () => { describe("vehicle-style.vue", () => {
test("BackButtonAction triggers a router.navigate change", async (done) => { test("BackButtonAction triggers a router.navigate change", async (done) => {
//Arrange //Arrange
@ -270,38 +179,13 @@ function setupMocks({
initializeComponent: jest.fn(), initializeComponent: jest.fn(),
}; };
funnelHeader.methods = {
initializeComponent: jest.fn(),
};
vehicleBanner.methods = {
initializeComponent: jest.fn(),
};
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleStyle, mountOptions); const wrapper = shallowMount(vehicleStyle, mountOptions);
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
const styleQuestionWrapper = wrapper.findComponent({ name: "styleQuestion" }); const styleQuestionWrapper = wrapper.findComponent({ name: "styleQuestion" });
styleQuestionWrapper.vm.initializeComponent = styleQuestionWrapper.vm.initializeComponent =
styleQuestion.methods.initializeComponent; styleQuestion.methods.initializeComponent;
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
funnelHeaderWrapper.vm.initializeComponent =
funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent =
vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({
name: "funnelSubHeader",
});
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,16 +1,16 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-0 position-relative"> <div class="container-fluid shadow rounded-3 p-0 position-relative">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<div class="select-car"> <div class="select-car">
<div class="select-car-form rounded text-center"> <div class="select-car-form rounded text-center">
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=true /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
<funnelSubHeader <funnelSubHeader
ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget"
:hasBackButton="true" :hasBackButton="true"
backButtonAccessibleText="Change Vehicle Model" backButtonAccessibleText="Change Vehicle Model"
@click-event="backButtonAction" @click-event="backButtonAction"
/> />
<styleQuestion v-model="selectedStyle" ref="styleQuestion" /> <styleQuestion v-model="selectedStyle" ref="styleQuestion" cmsWidgetName="VehicleStyleQuestion" />
</div> </div>
</div> </div>
</div> </div>
@ -60,17 +60,8 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.styleQuestion.initializeComponent( vm.$refs.styleQuestion.initializeComponent(
resultMap.cmsContent.VehicleStyleQuestion,
resultMap.styleQuestionInitialData resultMap.styleQuestionInitialData
); );
}); });

View file

@ -5,12 +5,10 @@ import { nextTick } from "vue";
import { storeMutations } from "@/constants/store-mutations"; import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "@/mixins/base-mixin.js";
import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue"; import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue";
import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; import yearQuestion from "@/layouts/vehicle-year/year-question/year-question";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
import store from "@/store"; import store from "@/store";
@ -29,16 +27,11 @@ jest.mock("@/helpers/cms-content-helper", () => ({
fetchCmsContentForPage: jest.fn(), fetchCmsContentForPage: jest.fn(),
})); }));
describe("vehicle-year.vue", () => { describe("vehicle-year.vue", () => {
test("Year question component is initized with api data", async (done) => { test("Year question component is initized with api data", async (done) => {
//Arrange //Arrange
const vehicleYearQuestionCmsContent = {
QuestionText: "What year is your vehicle?",
};
const yearQuestionInitialData = ["2023", "2022", "2021"]; const yearQuestionInitialData = ["2023", "2022", "2021"];
const { wrapper, apiPromise } = setupMocks({ const { wrapper, apiPromise } = setupMocks({
vehicleYearQuestionCmsContent: vehicleYearQuestionCmsContent,
yearQuestionInitialData: yearQuestionInitialData, yearQuestionInitialData: yearQuestionInitialData,
}); });
@ -53,7 +46,6 @@ describe("vehicle-year.vue", () => {
//Assert //Assert
apiPromise.finally(() => { apiPromise.finally(() => {
expect(yearQuestion.methods.initializeComponent).toHaveBeenCalledWith( expect(yearQuestion.methods.initializeComponent).toHaveBeenCalledWith(
vehicleYearQuestionCmsContent,
yearQuestionInitialData yearQuestionInitialData
); );
done(); done();
@ -61,64 +53,6 @@ describe("vehicle-year.vue", () => {
}); });
}); });
describe("vehicle-year.vue", () => {
test("Page logo image is initailized with api data", async (done) => {
//Arrange
const SiteHeaderWidget = {
LogoImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
};
const { wrapper, apiPromise } = setupMocks({
SiteHeaderWidget: SiteHeaderWidget,
});
//Act
vehicleYear.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-year" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(funnelHeader.methods.initializeComponent).toHaveBeenCalledWith(
SiteHeaderWidget
);
done();
});
});
});
describe("vehicle-year.vue", () => {
test("Vehicle image is initailized with api data", async (done) => {
//Arrange
const VehicleBannerWidget = {
GenericVehicleImage:
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
};
const { wrapper, apiPromise } = setupMocks({
VehicleBannerWidget: VehicleBannerWidget,
});
//Act
vehicleYear.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-year" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
apiPromise.finally(() => {
expect(vehicleBanner.methods.initializeComponent).toHaveBeenCalledWith(
VehicleBannerWidget
);
done();
});
});
});
describe("vehicle-year.vue", () => { describe("vehicle-year.vue", () => {
test("arePagePrerequisitesValid should be true ", async () => { test("arePagePrerequisitesValid should be true ", async () => {
//Arrange //Arrange
@ -200,32 +134,13 @@ function setupMocks({
loadInitialData: jest.fn(), loadInitialData: jest.fn(),
initializeComponent: jest.fn(), initializeComponent: jest.fn(),
}; };
funnelHeader.methods = {
initializeComponent: jest.fn(),
};
vehicleBanner.methods = {
initializeComponent: jest.fn(),
};
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
const mountOptions = getMountOptions(mountOptionsMockData); const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleYear, mountOptions); const wrapper = shallowMount(vehicleYear, mountOptions);
const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" }); const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" });
yearQuestionWrapper.vm.initializeComponent = yearQuestionWrapper.vm.initializeComponent =
yearQuestion.methods.initializeComponent; yearQuestion.methods.initializeComponent;
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" }); wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
funnelHeaderWrapper.vm.initializeComponent =
funnelHeader.methods.initializeComponent;
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
vehicleBannerWrapper.vm.initializeComponent =
vehicleBanner.methods.initializeComponent;
const funnelSubHeaderWrapper = wrapper.findComponent({
name: "funnelSubHeader",
});
funnelSubHeaderWrapper.vm.initializeComponent =
funnelSubHeader.methods.initializeComponent;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="container-fluid shadow rounded-3 p-0 position-relative"> <div class="container-fluid shadow rounded-3 p-0 position-relative">
<funnelHeader ref="funnelHeader" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<div class="select-car"> <div class="select-car">
<div class="select-car-form rounded text-center"> <div class="select-car-form rounded text-center">
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=true /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
<funnelSubHeader ref="funnelSubHeader" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<yearQuestion v-model="selectedYear" ref="yearQuestion" /> <yearQuestion v-model="selectedYear" ref="yearQuestion" cmsWidgetName="VehicleYearQuestion" />
</div> </div>
</div> </div>
</div> </div>
@ -55,17 +55,8 @@ export default {
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.$refs.funnelSubHeader.initializeComponent( vm.setCmsContent(resultMap.cmsContent);
resultMap.cmsContent.FunnelSubHeaderWidget
);
vm.$refs.funnelHeader.initializeComponent(
resultMap.cmsContent.FunnelHeaderWidget
);
vm.$refs.vehicleBanner.initializeComponent(
resultMap.cmsContent.VehicleBannerWidget
);
vm.$refs.yearQuestion.initializeComponent( vm.$refs.yearQuestion.initializeComponent(
resultMap.cmsContent.VehicleYearQuestion,
resultMap.yearQuestionInitialData resultMap.yearQuestionInitialData
); );
}); });

View file

@ -27,26 +27,6 @@ describe("year-question.vue", () => {
}); });
}); });
describe("year-question.vue", () => {
test("CMS question text is used as radio question text.", async () => {
//Arrange
const { wrapper, cmsContent } = setupMocks({
cmsQuestionText: "What year is your vehicle?",
});
//Act
yearQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, null);
//Assert
const buttonQuestionComponent = await wrapper.findComponent({
name: "buttonQuestion",
});
expect(buttonQuestionComponent.attributes("questiontext")).toBe(
"What year is your vehicle?"
);
});
});
describe("year-question.vue", () => { describe("year-question.vue", () => {
test("Data from store api are used as radio question answers.", async () => { test("Data from store api are used as radio question answers.", async () => {
//Arrange //Arrange
@ -58,7 +38,6 @@ describe("year-question.vue", () => {
const initialData = yearQuestion.methods.loadInitialData.call(wrapper.vm); const initialData = yearQuestion.methods.loadInitialData.call(wrapper.vm);
yearQuestion.methods.initializeComponent.call( yearQuestion.methods.initializeComponent.call(
wrapper.vm, wrapper.vm,
cmsContent,
initialData initialData
); );
@ -87,10 +66,16 @@ function setupMocks({
}); });
//Mock props //Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = { mountOptions.propsData = {
modelValue: modelValueProp, modelValue: modelValueProp,
}; };
mountOptions.mixins = [mockMixin];
const wrapper = shallowMount(yearQuestion, mountOptions); const wrapper = shallowMount(yearQuestion, mountOptions);
//Mock CMS content //Mock CMS content

View file

@ -21,18 +21,20 @@ export default {
name: "year-question", name: "year-question",
data() { data() {
return { return {
questionText: null,
years: Array, years: Array,
}; };
}, },
props: { props: {
modelValue: String, modelValue: String,
cmsWidgetName: String,
}, },
emits: ['update:modelValue'],
components: { components: {
buttonQuestion, buttonQuestion,
}, },
computed: { computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValueAsArray: { selectedValueAsArray: {
get: function() { get: function() {
const modelValueAsArray = this.modelValue ? [this.modelValue] : []; const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
@ -51,8 +53,7 @@ export default {
{} {}
); );
}, },
initializeComponent(cmsContent, initialData) { initializeComponent(initialData) {
this.questionText = cmsContent.QuestionText;
this.years = initialData; this.years = initialData;
}, },
}, },

View file

@ -6,9 +6,17 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
export default { export default {
data() { data() {
return {}; return {
cmsContentByWidget: {}
};
}, },
methods: { methods: {
setCmsContent(cmsContent){
this.$root.cmsContentByWidget = cmsContent;
},
getCmsContent(widgetName, fieldName){
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : '';
},
dispatchNonBlockingStoreAction(type, payload, encodePayload = true) { dispatchNonBlockingStoreAction(type, payload, encodePayload = true) {
// Encode the payload if required // Encode the payload if required
if (encodePayload) { if (encodePayload) {

View file

@ -45,8 +45,7 @@ const routes = [
await GoToFunnelStartOn404(next); await GoToFunnelStartOn404(next);
} }
// Process funnel cookie.
updateOrCreateFunnelCookie();
// On entering the funnel "fresh", read cookie information, decide what to do next. // On entering the funnel "fresh", read cookie information, decide what to do next.
if (from.redirectedFrom === undefined) { if (from.redirectedFrom === undefined) {
@ -64,6 +63,9 @@ const routes = [
to.query.fmgPage = pageToRedirectTo; to.query.fmgPage = pageToRedirectTo;
} }
// Process funnel cookie.
updateOrCreateFunnelCookie();
// If we already have our route, go to it. // If we already have our route, go to it.
if (router.hasRoute(to.query.fmgPage)) { if (router.hasRoute(to.query.fmgPage)) {
// Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function. // Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function.

View file

@ -8,7 +8,9 @@ const fmgPageValues = {
VIN_LOOKUP: "vin-lookup", VIN_LOOKUP: "vin-lookup",
VEHICLE_PARTS: "vehicle-parts", VEHICLE_PARTS: "vehicle-parts",
PART_QUESTIONS: "part-questions", PART_QUESTIONS: "part-questions",
REVEAL : "reveal", LICENSE_PLATE_LOOKUP: "license-plate-lookup",
REVEAL: "reveal",
ESTIMATE: "estimate",
}; };
export { fmgPageValues }; export { fmgPageValues };

View file

@ -8,7 +8,10 @@ const navigationScenarios = {
SELECTED_PARTS: "SELECTED_PARTS", SELECTED_PARTS: "SELECTED_PARTS",
SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART", SELECTED_DAMAGE_WITH_SINGLE_PART: "SELECTED_DAMAGE_WITH_SINGLE_PART",
SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS", SELECTED_DAMAGE_WITH_MULTIPLE_PARTS: "SELECTED_DAMAGE_WITH_MULTIPLE_PARTS",
SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS" SELECTED_DAMAGE_WITH_PART_QUESTIONS: "SELECTED_DAMAGE_WITH_PART_QUESTIONS",
CONTINUING_WITH_PARTS_QUESTION: "CONTINUING_WITH_PARTS_QUESTION",
CONTINUING_WITH_MULTIPLE_PARTS: "CONTINUING_WITH_MULTIPLE_PARTS",
CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART"
}; };
export { navigationScenarios }; export { navigationScenarios };

View file

@ -115,6 +115,27 @@ const routingTable = [
}, },
], ],
}, },
{
fmgPageValue: fmgPageValues.LICENSE_PLATE_LOOKUP,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationFmgPageValue: fmgPageValues.ESTIMATE,
},
{
scenario: navigationScenarios.CONTINUING_WITH_PARTS_QUESTION,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
},
{
scenario: navigationScenarios.CONTINUING_WITH_MULTIPLE_PARTS,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
},
{
scenario: navigationScenarios.CONTINUING_WITH_SINGLE_PART,
destinationFmgPageValue: fmgPageValues.REVEAL,
},
],
},
]; ];
export { routingTable }; export { routingTable };

View file

@ -90,7 +90,10 @@ export const mutations = {
updateVehicleImageColor(state, imageColor) { updateVehicleImageColor(state, imageColor) {
state.order.vehicle.imageColor = imageColor; state.order.vehicle.imageColor = imageColor;
}, },
updateIsRepair(state, isRepair) { updateVehicleVin(state, vin) {
state.order.vehicle.vin = vin;
},
updateIsRepair(state, isRepair){
state.order.damage.isRepair = isRepair; state.order.damage.isRepair = isRepair;
}, },
updateNumberOfChips(state, numberOfChips) { updateNumberOfChips(state, numberOfChips) {
@ -169,7 +172,7 @@ export const mutations = {
}, },
// Misc Mutations // Misc Mutations
setLoadOrderInformation(state, orderInformation) { updateStateWithOrderInformation(state, orderInformation) {
state.order.referralNumber = orderInformation.referralNumber; state.order.referralNumber = orderInformation.referralNumber;
state.order.referralDate = orderInformation.referralDate; state.order.referralDate = orderInformation.referralDate;
state.order.referralCorrelationId = orderInformation.referralCorrelationId; state.order.referralCorrelationId = orderInformation.referralCorrelationId;
@ -189,6 +192,7 @@ export const mutations = {
state.order.damage.numberOfChips = orderInformation.numberOfChips; state.order.damage.numberOfChips = orderInformation.numberOfChips;
state.order.lineItems.glassParts = orderInformation.parts; state.order.lineItems.glassParts = orderInformation.parts;
state.order.parentAccountNumber = orderInformation.parentAccountNumber; state.order.parentAccountNumber = orderInformation.parentAccountNumber;
state.order.serviceLocation.zipCode = orderInformation.zipCode; // TODO CSR-416 Make sure this is correct
} }
} }
@ -239,6 +243,16 @@ export const actions = {
}, },
}); });
}, },
lookupVinByPlate(context, { licensePlate, licenseState }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVinByPlate.method,
endpoint: endpoints.LookupVinByPlate.url,
payload: {
licensePlate: licensePlate,
licenseState: licenseState
},
});
},
getVehicleMakes(context, { year }) { getVehicleMakes(context, { year }) {
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method, method: endpoints.GetVehicleMakes.method,
@ -283,6 +297,12 @@ export const actions = {
payload: {}, payload: {},
}); });
}, },
validateZip(context, {zip}) {
return globalMethods.callHttpClient({
methods: endpoints.ValidateZip.method,
endpoint: `${endpoints.ValidateZip.url}/${zip}`
})
},
// DEPENDENCY ACTIONS // DEPENDENCY ACTIONS
resetVehicleAndDependencies(context) { resetVehicleAndDependencies(context) {
@ -374,6 +394,7 @@ export const actions = {
style: vehicle.style, style: vehicle.style,
}, },
numberOfChips: damage.numberOfChips, numberOfChips: damage.numberOfChips,
zipCode: 43215, // TODO CSR-416, should not be hardcoded (state.order.serviceLocation.zipCode)
glassToReplace: damage.glassToReplace, glassToReplace: damage.glassToReplace,
referralNumber: context.state.order.referralNumber, referralNumber: context.state.order.referralNumber,
referralDate: context.state.order.referralDate referralDate: context.state.order.referralDate
@ -391,7 +412,7 @@ export const actions = {
referralCorrelationId: referralCorrelationId referralCorrelationId: referralCorrelationId
}, },
}).then((response) => { }).then((response) => {
context.commit(storeMutations.SET_LOAD_FUNNEL_SESSION_INFO, response.data); context.commit(storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, response.data);
return response; return response;
}); });
} }

View file

@ -200,12 +200,12 @@ describe("Mutations", () => {
expect(storeState.applicationUser.pageData['vehicle-year']).toEqual({}); expect(storeState.applicationUser.pageData['vehicle-year']).toEqual({});
}); });
it("setLoadOrderInformation, should set order information in state", () => { it("updateStateWithOrderInformation, should set order information in state", () => {
// Arrange // Arrange
const storeState = state; const storeState = state;
// Act // Act
mutations.setLoadOrderInformation(storeState, { mutations.updateStateWithOrderInformation(storeState, {
referralNumber: 123, referralNumber: 123,
referralDate: new Date().toUTCString(), referralDate: new Date().toUTCString(),
referralCorrelationId: "xxx-xxx-xxx", referralCorrelationId: "xxx-xxx-xxx",
@ -283,6 +283,22 @@ describe("Actions", () => {
expect(response.data).toEqual({ carId: "C00000001" }); expect(response.data).toEqual({ carId: "C00000001" });
}); });
it("lookupVinByPlate action, should return car data", async () => {
// Arrange
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { carId: "C00000001" } });
});
// Assert
const response = await actions.lookupVinByPlate(context, "12345678901234567")
expect(response.data).toEqual({ carId: "C00000001" });
});
it("getVehicleMakes action, should return makes list", async () => { it("getVehicleMakes action, should return makes list", async () => {
// Arrange // Arrange
@ -368,6 +384,22 @@ describe("Actions", () => {
expect(response.data).toEqual(["Windshield", "DriversFrontDoor"]); expect(response.data).toEqual(["Windshield", "DriversFrontDoor"]);
}); });
it("validateZip action", async () => {
// Arrange
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: "43201" });
});
const response = await actions.validateZip(context, "C00000000")
// Assert
expect(response.data).toEqual("43201");
});
it("resetVehicleAndDependencies action", async () => { it("resetVehicleAndDependencies action", async () => {
// Arrange // Arrange
@ -432,6 +464,21 @@ describe("Actions", () => {
}); });
it("resetState action", async () => {
// Arrange
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetState(context)
expect(commit).toBeCalledWith(storeMutations.RESET_STATE);
});
it("getRouteInfo action, returns route info", async () => { it("getRouteInfo action, returns route info", async () => {
// Arrange // Arrange
@ -539,7 +586,7 @@ describe("Actions", () => {
// Assert // Assert
expect(response.data).toEqual({ referralNumber: 123 }); expect(response.data).toEqual({ referralNumber: 123 });
expect(commit).toBeCalledWith(storeMutations.SET_LOAD_FUNNEL_SESSION_INFO, {"referralNumber": 123}); expect(commit).toBeCalledWith(storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, {"referralNumber": 123});
}); });
it("setReferralInformation, should call commit three times", () => { it("setReferralInformation, should call commit three times", () => {

View file

@ -32,6 +32,9 @@ export default {
}; };
}, },
methods: { methods: {
removeLoader(){
this.isLoaderDisplayed = false;
},
clicked() { clicked() {
if (!this.isDisabled) { if (!this.isDisabled) {
this.isLoaderDisplayed = true; this.isLoaderDisplayed = true;

View file

@ -188,7 +188,7 @@ export default {
input[type="radio"] { input[type="radio"] {
opacity: 0; opacity: 0;
width: 0; width: 0;
height: 0; height: 0.1px; // NOTE: cannot be zero or safari can't put focus on it
position: absolute; position: absolute;
+ label { + label {