Merge branch 'develop' into CSR-1357-customer-details

This commit is contained in:
bmauger 2023-05-17 14:41:32 -04:00 committed by GitHub
commit b8aec195ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 770 additions and 380 deletions

View file

@ -28,4 +28,6 @@ module.exports = {
// 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 // 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
}, },
}, },
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
// silent: true,
}; };

View file

@ -90,7 +90,11 @@ const endpoints = {
url: "/parts/api/v1/parts/supporting-items", url: "/parts/api/v1/parts/supporting-items",
method: "POST", method: "POST",
}, },
GetProviderLocations: { GetAlertReasons: {
url: "/location/api/v1/location/alert-reasons",
method: "GET",
},
GetProviders: {
url: "/location/api/v1/location/providers", url: "/location/api/v1/location/providers",
method: "GET", method: "GET",
}, },

View file

@ -21,6 +21,7 @@ const storeActions = {
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate", LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress", LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
LOOKUP_VIN_BY_IMAGE: "lookupVinByImage", LOOKUP_VIN_BY_IMAGE: "lookupVinByImage",
GET_ALERT_REASONS_BY_CTU: "getAlertReasonsByCtu",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
GET_PARTS: "getParts", GET_PARTS: "getParts",
GET_WIPERS: "getWipers", GET_WIPERS: "getWipers",
@ -31,7 +32,7 @@ const storeActions = {
GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOLDING_QUESTIONS: "getMoldingQuestions",
GET_MOBILE_FEE_PART: "getMobileFeePart", GET_MOBILE_FEE_PART: "getMobileFeePart",
GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails", GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails",
GET_PROVIDER_LOCATIONS: "getProviderLocations", GET_PROVIDERS: "getProviders",
SAVE_SESSION: "saveSession", SAVE_SESSION: "saveSession",
LOAD_SESSION: "loadSession", LOAD_SESSION: "loadSession",
UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse", UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse",

View file

@ -121,7 +121,7 @@ export default {
isOverflowScrollable: Boolean, isOverflowScrollable: Boolean,
isWide: Boolean, isWide: Boolean,
isCashOrInsurance: Boolean, isCashOrInsurance: Boolean,
modelValue: [Array, Number, String], modelValue: [Array, Number, String, Object],
value: [Number, String], value: [Number, String],
validationRules: String, validationRules: String,
suppressError: Boolean, suppressError: Boolean,
@ -129,7 +129,6 @@ export default {
valueToLogType: String, valueToLogType: String,
additionalButtonStyling: String, additionalButtonStyling: String,
isSmallQuestionText: Boolean, isSmallQuestionText: Boolean,
availability: String,
customButtonQuestionId: String, customButtonQuestionId: String,
logDisplayedValuesEvent: { logDisplayedValuesEvent: {
type: Boolean, type: Boolean,

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="text-block w-100 mt-2" class="text-block w-100"
:class="[justifyText, typeStyle, fontWeight]" :class="[justifyText, typeStyle, fontWeight, margin]"
v-html="this.TextBlockCopy"></div> v-html="this.TextBlockCopy"></div>
</template> </template>
@ -11,6 +11,11 @@ export default {
props: { props: {
customText: String, // used to allow the insert of token values into textblock customText: String, // used to allow the insert of token values into textblock
justifyText: String, // left, right, center justifyText: String, // left, right, center
margin: {
// bootstrap margin to apply to the block.
type: String,
default: "mt-2",
},
typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation) typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation)
fontWeight: String, // bold=500, default is 400 fontWeight: String, // bold=500, default is 400
cmsWidgetName: String, cmsWidgetName: String,

View file

@ -66,6 +66,7 @@
<script> <script>
import textLink from "@/ux-components/text-link/text-link"; import textLink from "@/ux-components/text-link/text-link";
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import baseMixin from "@/mixins/base-mixin.js";
export default { export default {
name: "menuModal", name: "menuModal",
@ -92,10 +93,7 @@ export default {
show() { show() {
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72; this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
this.isActive = true; this.isActive = true;
document.querySelector(".page-container-grouped-styles").scrollTo({ this.scrollToPageTop();
top: 0,
behavior: "smooth",
});
}, },
hide() { hide() {
var self = this; var self = this;

View file

@ -0,0 +1,3 @@
describe("Review Page", () => {
test.todo("Add more tests as specific functionality is added.");
});

View file

@ -0,0 +1,102 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
<!-- When customer-details is added: v-slot="{ meta }" -->
<div class="page-container-grouped-styles">
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<vehicleBanner
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<textBlock
:customText="subHeaderTitle"
typeStyle="h5"
justifyText="text-center"
margin="mt-1"
class="dark-header" />
<textBlock
:customText="subHeaderBody"
typeStyle="body"
justifyText="left"
margin="mt-0 mb-2" />
<buttonMain
ref="buttonMain"
isPrimary
:buttonText="forwardButtonText"
loaderColor="white"
@click-event="forwardButtonAction" />
<funnelFooter
cmsWidgetName="FunnelFooterWidget"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</Form>
</template>
<script>
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
import buttonMain from "@/ux-components/button-main/button-main";
import textBlock from "@/digital-components/text-block/text-block";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
export default {
name: "review",
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);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
data() {
return {};
},
methods: {
arePagePrerequisitesValid() {
return true;
},
backButtonAction() {},
forwardButtonAction() {},
},
computed: {
subHeaderTitle() {
return this.getCmsContent("FunnelSubHeaderWidget", "HeaderText");
},
subHeaderBody() {
return this.getCmsContent("FunnelSubHeaderWidget", "BodyText");
},
forwardButtonText() {
return this.getCmsContent("FunnelFooterWidget", "ForwardButtonText");
},
},
components: {
funnelHeader,
funnelFooter,
vehicleBanner,
buttonMain,
textBlock,
},
};
</script>
<style lang="scss" scoped>
.dark-header {
color: $black;
}
</style>

View file

@ -0,0 +1,13 @@
import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
export async function getAlertReasons(ctu) {
const alertReasons = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_ALERT_REASONS_BY_CTU,
{
ctu: ctu,
}
);
return Promise.resolve(alertReasons);
}

View file

@ -18,6 +18,17 @@
<span v-else class="m-0 text-body" v-html="copy"></span> <span v-else class="m-0 text-body" v-html="copy"></span>
</span> </span>
</div> </div>
<template v-if="displayWeatherAlert">
<alert
v-for="alert in weatherAlerts"
v-show="cmsHeadlineTextFound(alert.cmsWidgetName)"
:key="alert.cmsWidgetName"
:ref="alert.cmsWidgetName"
class="mt-2 mb-3"
:cmsWidgetName="alert.cmsWidgetName"
alertClass="alert-warning" />
</template>
<date-picker <date-picker
selectableDates="custom" selectableDates="custom"
v-model="selectedDate" v-model="selectedDate"
@ -34,6 +45,7 @@
<script> <script>
// Components // Components
import alert from "@/ux-components/alert/alert";
import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer"; import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
@ -45,6 +57,7 @@ import datePicker from "@/digital-components/date-picker/date-picker";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper"; import { settleAllPromises } from "@/helpers/layout-helper";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
import { getAlertReasons } from "@/layouts/schedule/helpers/schedule-helper";
import { import {
doesCopyContainRouterLink, doesCopyContainRouterLink,
splitCopyOnCMSPlaceHolder, splitCopyOnCMSPlaceHolder,
@ -53,6 +66,7 @@ import {
} from "@/helpers/cms-content-helper"; } from "@/helpers/cms-content-helper";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import store from "@/store";
defineRule("date-required", required(errorMessages.DATE_REQUIRED)); defineRule("date-required", required(errorMessages.DATE_REQUIRED));
@ -61,6 +75,7 @@ export default {
data() { data() {
return { return {
selectedDate: null, selectedDate: null,
weatherAlerts: [],
mockSelectableDatesData: [ mockSelectableDatesData: [
{ year: 2023, month: 4, date: 13 }, { year: 2023, month: 4, date: 13 },
{ year: 2023, month: 4, date: 14 }, { year: 2023, month: 4, date: 14 },
@ -79,12 +94,18 @@ export default {
// Call APIs // Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const alertReasonsPromise = getAlertReasons(store.getters.order.serviceLocation.zipCodeCtu);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
{ {
resultKey: "cmsContent", resultKey: "cmsContent",
promise: cmsContentPromise, promise: cmsContentPromise,
}, },
{
resultKey: "alertReasons",
promise: alertReasonsPromise,
},
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
@ -92,6 +113,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.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.setData(resultMap.alertReasons);
}); });
}, },
computed: { computed: {
@ -102,6 +124,9 @@ export default {
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed // Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText); return this.splitCopyOnCMSPlaceHolder(this.ChangeShopLinkText);
}, },
displayWeatherAlert() {
return this.weatherAlerts.length > 0;
},
}, },
methods: { methods: {
doesCopyContainRouterLink, doesCopyContainRouterLink,
@ -112,9 +137,40 @@ export default {
return true; return true;
// NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE? // NEED TODO - WHAT ARE PAGE REQ'S FOR THIS PAGE?
}, },
setData(alertReasonsData) {
if (alertReasonsData) {
this.convertReasonsToCmsAlerts(alertReasonsData);
}
},
cmsHeadlineTextFound(widgetName) {
return this.getCmsContent(widgetName, "HeadlineText") !== "";
},
convertReasonsToCmsAlerts(data) {
this.weatherAlerts = data.reduce((newObj, alert) => {
newObj.push({
cmsWidgetName: `LocationAlert-${alert}`,
alertReason: alert,
});
return newObj;
}, []);
},
async getWeatherAlertReasons(ctu) {
await getAlertReasons(ctu)
.then((response) => {
if (response.data) {
this.convertReasonsToCmsAlerts(response.data);
}
})
.catch(() => {
console.log("error fetching alert reasons..");
});
},
getAvailableDates(startDate, endDate) { getAvailableDates(startDate, endDate) {
return this.mockSelectableDatesData; return this.mockSelectableDatesData;
}, },
getServiceZipCtuCodeFromStore() {
return store.getters.order.serviceLocation.zipCodeCtu;
},
backButtonAction() { backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },
@ -123,6 +179,7 @@ export default {
}, },
}, },
components: { components: {
alert,
funnelHeader, funnelHeader,
funnelFooter, funnelFooter,
funnelSubHeader, funnelSubHeader,
@ -130,6 +187,7 @@ export default {
loadingModal, loadingModal,
datePicker, datePicker,
}, },
mounted() {},
}; };
</script> </script>

View file

@ -137,6 +137,9 @@ export default {
alertInvalidZipWidgetName: String, alertInvalidZipWidgetName: String,
customComponentId: String, customComponentId: String,
validationRules: String, validationRules: String,
onZipUpdateCallback: {
type: Function,
},
}, },
computed: { computed: {
mobileLocationLinkPromptText() { mobileLocationLinkPromptText() {
@ -257,6 +260,10 @@ export default {
// Update the page level model // Update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);
if (this.onZipUpdateCallback) {
await this.onZipUpdateCallback(serviceZipCode);
}
this.closeModal(); this.closeModal();
} }
}, },

View file

@ -186,7 +186,7 @@ beforeEach(() => {
describe("service-location.vue", () => { describe("service-location.vue", () => {
describe("beforeRouteEnter", () => { describe("beforeRouteEnter", () => {
test("on load sets the mobile fee part when an service zip code has already been provided", async () => { test("on load sets the mobile fee part when a service zip code has already been provided", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});

View file

@ -12,7 +12,8 @@
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
linkWidgetName="ServiceZipLinkWidget" linkWidgetName="ServiceZipLinkWidget"
modalWidgetName="ServiceZipModalWidget" /> modalWidgetName="ServiceZipModalWidget"
:onZipUpdateCallback="reloadShopData" />
<alert <alert
ref="alertMilitaryBaseZip" ref="alertMilitaryBaseZip"
class="my-5" class="my-5"
@ -64,17 +65,17 @@
validationRules="mobile-location-required" validationRules="mobile-location-required"
ref="mobileLocationQuestions" ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget" linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget" /> modalWidgetName="MobileLocationModalWidget"
:onZipUpdateCallback="reloadShopData" />
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
<shopQuestion <shopQuestion
ref="shopQuestion" ref="shopQuestion"
v-show=" v-show="isShopQuestionDisplayed"
selectedAppointmentType === 'Inshop' || v-model="selectedProviderNumber"
selectedAppointmentType === 'Dropoff' @providerSelected="onProviderSelected"
"
v-model="providerNumber"
:serviceZipCode="zipCode" :serviceZipCode="zipCode"
:selectedAppointmentType="selectedAppointmentType" :selectedAppointmentType="selectedAppointmentType"
:isDisplayed="isShopQuestionDisplayed"
cmsWidgetName="ShopQuestionWidget" /> cmsWidgetName="ShopQuestionWidget" />
</Transition> </Transition>
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" /> <contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
@ -133,6 +134,14 @@ defineRule("mobile-location-required", (value) => {
return true; return true;
}); });
const defaultProvider = {
providerNumber: null,
address: null,
city: null,
state: null,
zip: null,
};
export default { export default {
name: "service-location", name: "service-location",
data() { data() {
@ -147,10 +156,12 @@ export default {
isRecalibrationServiceableInshop: null, isRecalibrationServiceableInshop: null,
isGlassServiceableMobile: null, isGlassServiceableMobile: null,
isRecalibrationServiceableMobile: null, isRecalibrationServiceableMobile: null,
selectedAppointmentType: null, selectedAppointmentType: this.getSelectedAppointmentType(),
providerNumber: null, selectedProvider: this.getSelectedProvider(),
selectedProviderNumber: this.getSelectedProvider().providerNumber,
mobileFeePart: null, mobileFeePart: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
zipCodeCtu: null,
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -164,7 +175,7 @@ export default {
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(); const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
@ -215,7 +226,7 @@ export default {
if (newValue.zipCode !== this.zipCode) { if (newValue.zipCode !== this.zipCode) {
this.resetMobileLocation(); this.resetMobileLocation();
this.selectedAppointmentType = null; this.selectedAppointmentType = null;
this.providerNumber = null; this.selectedProvider = defaultProvider;
} }
this.state = newValue.state; this.state = newValue.state;
@ -250,7 +261,7 @@ export default {
if (!this.selectedAppointmentType == "Mobile") { if (!this.selectedAppointmentType == "Mobile") {
this.selectedAppointmentType = null; this.selectedAppointmentType = null;
} }
this.providerNumber = null; this.selectedProvider = defaultProvider;
} }
}, },
}, },
@ -268,6 +279,12 @@ export default {
return this.isGlassServiceableInshop; return this.isGlassServiceableInshop;
} }
}, },
isShopQuestionDisplayed() {
return (
this.selectedAppointmentType === "Inshop" ||
this.selectedAppointmentType === "Dropoff"
);
},
// Specifically check for isRecalibrationServiceableMobile === false, not null or true. // Specifically check for isRecalibrationServiceableMobile === false, not null or true.
requiresInshopRecalibration() { requiresInshopRecalibration() {
return ( return (
@ -304,9 +321,10 @@ export default {
store.getters.payment.isInsurance !== null store.getters.payment.isInsurance !== null
); );
}, },
setData(zipCodeData, serviceabilityDetails, mobileFeePart, shopQuestionData) { setData(zipCodeData, serviceabilityDetails, mobileFeePart) {
if (zipCodeData) { if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase; this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu;
} }
if (serviceabilityDetails) { if (serviceabilityDetails) {
@ -334,6 +352,12 @@ export default {
getServiceZipCodeFromStore() { getServiceZipCodeFromStore() {
return store.getters.order.serviceLocation.zipCode; return store.getters.order.serviceLocation.zipCode;
}, },
getSelectedAppointmentType() {
return store.getters.order.serviceLocation.appointmentType;
},
getSelectedProvider() {
return store.getters.order.serviceLocation.provider ?? defaultProvider;
},
setMobileFeePart(mobileFeePart) { setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart; this.mobileFeePart = mobileFeePart;
}, },
@ -355,8 +379,30 @@ export default {
backButtonAction() { backButtonAction() {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },
forwardButtonAction() { async forwardButtonAction() {
this.$router.navigateWithoutSaving( await this.dispatchStoreAction(
this.storeActions.SAVE_SERVICE_LOCATION,
{
address: this.streetAddress,
address2: this.apartmentNumberOrBusinessName,
city: this.city,
state: this.state,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
provider: {
providerNumber: this.selectedProvider?.providerNumber,
address: this.selectedProvider?.address?.streetAddress,
city: this.selectedProvider?.address?.city,
state: this.selectedProvider?.address?.state,
zip: this.selectedProvider?.address?.zipCode,
},
},
false
);
this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_LOCATION, this.navigationScenarios.SELECTED_LOCATION,
this.$route this.$route
); );
@ -364,6 +410,12 @@ export default {
openModalAction(modalName) { openModalAction(modalName) {
this.$refs[modalName].openModal(); this.$refs[modalName].openModal();
}, },
async reloadShopData() {
await this.$refs.shopQuestion.reloadShopData(this.zipCode);
},
onProviderSelected(selectedProvider) {
this.selectedProvider = selectedProvider;
},
}, },
components: { components: {
alert, alert,

View file

@ -69,6 +69,9 @@ export default {
}, },
linkWidgetName: String, linkWidgetName: String,
modalWidgetName: String, modalWidgetName: String,
onZipUpdateCallback: {
type: Function,
},
}, },
setup() { setup() {
const textboxQuestionWidgetName = "ServiceZipQuestionWidget"; const textboxQuestionWidgetName = "ServiceZipQuestionWidget";
@ -161,6 +164,10 @@ export default {
// Update the page level model // Update the page level model
this.$emit("update:modelValue", this.internalModel); this.$emit("update:modelValue", this.internalModel);
if (this.onZipUpdateCallback) {
await this.onZipUpdateCallback(serviceZipCode);
}
this.closeModal(); this.closeModal();
} }
} else { } else {

View file

@ -6,7 +6,7 @@ import shopQuestion from "./shop-question";
jest.mock("@/mixins/base-mixin", () => ({ jest.mock("@/mixins/base-mixin", () => ({
methods: { methods: {
dispatchStoreAction(action, items, encode) { dispatchStoreAction(action, items, encode) {
if (action === mockGetProviderLocationsStoreAction) { if (action === mockGetProvidersStoreAction) {
return new Promise((resolve) => { return new Promise((resolve) => {
resolve(mockNewShopList); resolve(mockNewShopList);
}); });
@ -16,35 +16,41 @@ jest.mock("@/mixins/base-mixin", () => ({
}, },
})); }));
const mockGetProviderLocationsStoreAction = storeActions.GET_PROVIDER_LOCATIONS; const mockGetProvidersStoreAction = storeActions.GET_PROVIDERS;
const mockNewShopList = { const mockNewShopList = {
data: [ shopProviders: [
{ {
city: "Far", address: {
country: "United States", city: "COLUMBUS",
distance: 100, country: "US",
providerNumber: "129", state: "OH",
state: "OH", streetAddress: "1670 HARMON AVE",
streetAddress: "555 First Capital Ln", zipCode: "43223",
zipCode: "45601", },
distanceInMiles: 15.9727889297435,
providerNumber: "006747",
}, },
{ {
city: "Farther", address: {
country: "United States", city: "POWELL",
distance: 200, country: "US",
providerNumber: "130", state: "OH",
state: "OH", streetAddress: "3938 POWELL RD",
streetAddress: "5486 N Grove Rd", zipCode: "43065",
zipCode: "43215", },
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
}, },
{ {
city: "Farthest (Ever)", address: {
country: "United States", city: "Columbus",
distance: 380.5, country: "US",
providerNumber: "131", state: "OH",
state: "OH", streetAddress: "4580 W Broad St",
streetAddress: "1670 Bongo Ave D", zipCode: "43228",
zipCode: "43223", },
distanceInMiles: 19.4618116611001,
providerNumber: "003342",
}, },
], ],
}; };
@ -53,90 +59,77 @@ const mockCmsContent = {
QuestionText: "Select a shop:", QuestionText: "Select a shop:",
}; };
const cmsWidgetName = "AppointmentTypeQuestionWidget"; const cmsWidgetName = "ShopQuestionWidget";
const shopQuestionInitialData = [ const shopQuestionInitialData = {
{ shopProviders: [
city: "Worthington", {
country: "United States", address: {
distance: 1.5, city: "WESTERVILLE",
providerNumber: "123", country: "US",
state: "OH", state: "OH",
streetAddress: "760 Dearborn Park Ln", streetAddress: "4403 EXECUTIVE PKWY",
zipCode: "43085", zipCode: "43081",
}, },
{ distanceInMiles: 5.16769294095201,
city: "Columbus", providerNumber: "003335",
country: "United States", },
distance: 4.5, {
providerNumber: "124", address: {
state: "OH", city: "WORTHINGTON",
streetAddress: "5486 N Hamilton Rd", country: "US",
zipCode: "43230", state: "OH",
}, streetAddress: "760 DEARBORN PARK LN",
{ zipCode: "43085",
city: "Powell", },
country: "United States", distanceInMiles: 10.5865432478478,
distance: 7, providerNumber: "001820",
providerNumber: "125", },
state: "OH", {
streetAddress: "1670 Harmon Ave C", address: {
zipCode: "43223", city: "COLUMBUS",
}, country: "US",
{ state: "OH",
city: "Chillicothe", streetAddress: "5015 N HIGH ST",
country: "United States", zipCode: "43214",
distance: 41.5, },
providerNumber: "126", distanceInMiles: 11.738869544543,
state: "OH", providerNumber: "003343",
streetAddress: "555 First Capital Ln", },
zipCode: "45601", {
}, address: {
{ city: "COLUMBUS",
city: "Grove City", country: "US",
country: "United States", state: "OH",
distance: 4.5, streetAddress: "1670 HARMON AVE",
providerNumber: "127", zipCode: "43223",
state: "OH", },
streetAddress: "5486 N Grove Rd", distanceInMiles: 15.9727889297435,
zipCode: "43215", providerNumber: "006747",
}, },
{ {
city: "Dayton", address: {
country: "United States", city: "POWELL",
distance: 80.5, country: "US",
providerNumber: "128", state: "OH",
state: "OH", streetAddress: "3938 POWELL RD",
streetAddress: "1670 Bongo Ave D", zipCode: "43065",
zipCode: "43223", },
}, distanceInMiles: 16.2690495685233,
{ providerNumber: "003341",
city: "Far", },
country: "United States", {
distance: 100, address: {
providerNumber: "129", city: "Columbus",
state: "OH", country: "US",
streetAddress: "555 First Capital Ln", state: "OH",
zipCode: "45601", streetAddress: "4580 W Broad St",
}, zipCode: "43228",
{ },
city: "Farther", distanceInMiles: 19.4618116611001,
country: "United States", providerNumber: "003342",
distance: 200, },
providerNumber: "130", ],
state: "OH", };
streetAddress: "5486 N Grove Rd",
zipCode: "43215",
},
{
city: "Farthest (Ever)",
country: "United States",
distance: 380.5,
providerNumber: "131",
state: "OH",
streetAddress: "1670 Bongo Ave D",
zipCode: "43223",
},
];
const mockMixin = { const mockMixin = {
methods: { methods: {
@ -151,14 +144,16 @@ const mockMixin = {
}; };
describe("shop-question.vue", () => { describe("shop-question.vue", () => {
it("Should display first three shops when an appointment type has already been selected", async () => { beforeEach(() => {
// Arrange/Act
const container = document.createElement("div"); const container = document.createElement("div");
container.scrollTo = jest.fn(); container.scrollTo = jest.fn();
container.classList.add("page-container-grouped-styles"); container.classList.add("page-container-grouped-styles");
document.body.appendChild(container); document.body.appendChild(container);
});
it("Should display first three shops when an appointment type has already been selected", async () => {
// Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
@ -166,192 +161,224 @@ describe("shop-question.vue", () => {
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // Assert
expect(wrapper.vm.answers.length).toEqual(3);
expect(wrapper.vm.answers).toEqual([ expect(wrapper.vm.answers).toEqual([
{ {
Name: "123", buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
buttonLabel: "Worthington", buttonLabel: "4403 Executive Pkwy",
buttonLabelSubCopy: "1.5 mi", buttonLabelSubCopy: "5 mi",
value: "003335",
},
{
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085", buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
buttonLabel: "760 Dearborn Park Ln",
buttonLabelSubCopy: "10.5 mi",
value: "001820",
}, },
{ {
Name: "124", buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
buttonLabel: "Columbus", buttonLabel: "5015 N High St",
buttonLabelSubCopy: "4.5 mi", buttonLabelSubCopy: "11.5 mi",
buttonBodyCopy: "5486 N Hamilton Rd, Columbus, OH 43230", value: "003343",
},
{
Name: "125",
buttonLabel: "Powell",
buttonLabelSubCopy: "7 mi",
buttonBodyCopy: "1670 Harmon Ave C, Powell, OH 43223",
}, },
]); ]);
}); });
it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => { it("Should display the 'Show more locations' link when there are more than three locations to chose from", async () => {
// Arrange/Act // Arrange
const container = document.createElement("div");
container.scrollTo = jest.fn();
container.classList.add("page-container-grouped-styles");
document.body.appendChild(container);
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: null, modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" }); const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
// Assert // Assert
expect(showMoreShopsLink.exists()).toBe(true); expect(showMoreShopsLink.exists()).toBe(true);
expect(showMoreShopsLink.isVisible()).toBe(true);
}); });
it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => { it("Should not display the 'Show more locations' link when there are fewer than three locations to chose from", async () => {
// Arrange/Act // Arrange
const container = document.createElement("div"); const alsoShopQuestionInitialData = {
container.scrollTo = jest.fn(); shopProviders: [
{
container.classList.add("page-container-grouped-styles"); address: {
document.body.appendChild(container); city: "WESTERVILLE",
country: "US",
const alsoShopQuestionInitialData = [ state: "OH",
{ streetAddress: "4403 EXECUTIVE PKWY",
city: "Worthington", zipCode: "43081",
country: "United States", },
distance: 1.5, distanceInMiles: 5.16769294095201,
providerNumber: "123", providerNumber: "003335",
state: "OH", },
streetAddress: "760 Dearborn Park Ln", {
zipCode: "43085", address: {
}, city: "WORTHINGTON",
{ country: "US",
city: "Columbus", state: "OH",
country: "United States", streetAddress: "760 DEARBORN PARK LN",
distance: 4.5, zipCode: "43085",
providerNumber: "124", },
state: "OH", distanceInMiles: 10.5865432478478,
streetAddress: "5486 N Hamilton Rd", providerNumber: "001820",
zipCode: "43230", },
}, {
{ address: {
city: "Powell", city: "COLUMBUS",
country: "United States", country: "US",
distance: 7, state: "OH",
providerNumber: "125", streetAddress: "5015 N HIGH ST",
state: "OH", zipCode: "43214",
streetAddress: "1670 Harmon Ave C", },
zipCode: "43223", distanceInMiles: 11.738869544543,
}, providerNumber: "003343",
]; },
],
};
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: null, modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(alsoShopQuestionInitialData); wrapper.vm.initializeComponent(alsoShopQuestionInitialData);
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" }); const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
// Assert // Assert
expect(showMoreShopsLink.exists()).toBe(false); expect(showMoreShopsLink.exists()).toBe(true);
expect(showMoreShopsLink.isVisible()).toBe(false);
}); });
it("Should display the next three shops when the 'Show more location' link is clicked", async () => { it("Should display the next three shops when the 'Show more location' link is clicked", async () => {
const container = document.createElement("div"); // Arrange
container.scrollTo = jest.fn();
container.classList.add("page-container-grouped-styles");
document.body.appendChild(container);
const displayedAnswers = [ const displayedAnswers = [
{ {
Name: "123", buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
buttonLabel: "Worthington", buttonLabel: "4403 Executive Pkwy",
buttonLabelSubCopy: "1.5 mi", buttonLabelSubCopy: "5 mi",
value: "003335",
},
{
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085", buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
buttonLabel: "760 Dearborn Park Ln",
buttonLabelSubCopy: "10.5 mi",
value: "001820",
}, },
{ {
Name: "124", buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
buttonLabel: "Columbus", buttonLabel: "5015 N High St",
buttonLabelSubCopy: "4.5 mi", buttonLabelSubCopy: "11.5 mi",
buttonBodyCopy: "5486 N Hamilton Rd, Columbus, OH 43230", value: "003343",
},
{
Name: "125",
buttonLabel: "Powell",
buttonLabelSubCopy: "7 mi",
buttonBodyCopy: "1670 Harmon Ave C, Powell, OH 43223",
}, },
]; ];
// Arrange/Act
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: null, modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
isDisplayed: true,
}, },
mountOptions: { mountOptions: {
attachTo: document.body, attachTo: document.body,
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick();
wrapper.vm.shops = shopQuestionInitialData;
wrapper.vm.answers = displayedAnswers; wrapper.vm.answers = displayedAnswers;
wrapper.vm.shopIndex = 3; wrapper.vm.shopIndex = 3;
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" }); const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
await wrapper.vm.$nextTick();
showMoreShopsLink.trigger("click"); showMoreShopsLink.trigger("click");
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -360,56 +387,54 @@ describe("shop-question.vue", () => {
expect(wrapper.vm.answers.length).toBe(6); expect(wrapper.vm.answers.length).toBe(6);
expect(wrapper.vm.answers).toEqual([ expect(wrapper.vm.answers).toEqual([
{ {
Name: "123", buttonBodyCopy: "4403 Executive Pkwy, Westerville, OH 43081",
buttonLabel: "Worthington", buttonLabel: "4403 Executive Pkwy",
buttonLabelSubCopy: "1.5 mi", buttonLabelSubCopy: "5 mi",
value: "003335",
},
{
buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085", buttonBodyCopy: "760 Dearborn Park Ln, Worthington, OH 43085",
buttonLabel: "760 Dearborn Park Ln",
buttonLabelSubCopy: "10.5 mi",
value: "001820",
}, },
{ {
Name: "124", buttonBodyCopy: "5015 N High St, Columbus, OH 43214",
buttonLabel: "Columbus", buttonLabel: "5015 N High St",
buttonLabelSubCopy: "4.5 mi", buttonLabelSubCopy: "11.5 mi",
buttonBodyCopy: "5486 N Hamilton Rd, Columbus, OH 43230", value: "003343",
}, },
{ {
Name: "125", buttonBodyCopy: "1670 Harmon Ave, Columbus, OH 43223",
buttonLabel: "Powell", buttonLabel: "1670 Harmon Ave",
buttonLabelSubCopy: "7 mi", buttonLabelSubCopy: "16 mi",
buttonBodyCopy: "1670 Harmon Ave C, Powell, OH 43223", value: "006747",
}, },
{ {
Name: "126", buttonBodyCopy: "3938 Powell Rd, Powell, OH 43065",
buttonLabel: "Chillicothe", buttonLabel: "3938 Powell Rd",
buttonLabelSubCopy: "41.5 mi", buttonLabelSubCopy: "16.5 mi",
buttonBodyCopy: "555 First Capital Ln, Chillicothe, OH 45601", value: "003341",
}, },
{ {
Name: "127", buttonBodyCopy: "4580 W Broad St, Columbus, OH 43228",
buttonLabel: "Grove City", buttonLabel: "4580 W Broad St",
buttonLabelSubCopy: "4.5 mi", buttonLabelSubCopy: "19.5 mi",
buttonBodyCopy: "5486 N Grove Rd, Grove City, OH 43215", value: "003342",
},
{
Name: "128",
buttonLabel: "Dayton",
buttonLabelSubCopy: "80.5 mi",
buttonBodyCopy: "1670 Bongo Ave D, Dayton, OH 43223",
}, },
]); ]);
}); });
it("Should display the number of shops necessary to show a previously selected shop", async () => { it("Should display the number of shops necessary to show a previously selected shop", async () => {
// Arrange/Act // Arrange
const container = document.createElement("div"); const selectedProvider = {
container.scrollTo = jest.fn(); providerNumber: "003341",
};
container.classList.add("page-container-grouped-styles");
document.body.appendChild(container);
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: "127", modelValue: selectedProvider.providerNumber,
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
@ -419,7 +444,7 @@ describe("shop-question.vue", () => {
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); wrapper.vm.initializeComponent(shopQuestionInitialData);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
@ -431,17 +456,21 @@ describe("shop-question.vue", () => {
}); });
it("Should reset the answers when the selected appointment type changes", async () => { it("Should reset the answers when the selected appointment type changes", async () => {
// Arrange/Act // Arrange
const container = document.createElement("div");
container.scrollTo = jest.fn();
container.classList.add("page-container-grouped-styles");
document.body.appendChild(container);
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
props: { props: {
modelValue: "127", modelValue: {
address: {
city: "POWELL",
country: "US",
state: "OH",
streetAddress: "3938 POWELL RD",
zipCode: "43065",
},
distanceInMiles: 16.2690495685233,
providerNumber: "003341",
},
serviceZipCode: "43081", serviceZipCode: "43081",
selectedAppointmentType: "Dropoff", selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName, cmsWidgetName: cmsWidgetName,
@ -451,10 +480,12 @@ describe("shop-question.vue", () => {
}, },
}); });
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // Act
wrapper.vm.initializeComponent(shopQuestionInitialData); wrapper.vm.initializeComponent(shopQuestionInitialData);
wrapper.setProps({ await wrapper.vm.$nextTick();
await wrapper.setProps({
selectedAppointmentType: "Inshop", selectedAppointmentType: "Inshop",
}); });
@ -464,79 +495,85 @@ describe("shop-question.vue", () => {
expect(wrapper.vm.answers.length).toEqual(3); expect(wrapper.vm.answers.length).toEqual(3);
}); });
it("Should reload the shops when the service zip code changes", async () => { // it("Should reload the shops when the service zip code changes", async () => {
// Arrange/Act // // Arrange
const container = document.createElement("div"); // const { wrapper } = setupMocks({
container.scrollTo = jest.fn(); // mixins: [mockMixin],
// props: {
// modelValue: {
// address: {
// city: "POWELL",
// country: "US",
// state: "OH",
// streetAddress: "3938 POWELL RD",
// zipCode: "43065",
// },
// distanceInMiles: 16.2690495685233,
// providerNumber: "003341",
// },
// serviceZipCode: "43081",
// selectedAppointmentType: "Dropoff",
// cmsWidgetName: cmsWidgetName,
// isDisplayed: true
// },
// mountOptions: {
// attachTo: document.body,
// },
// });
container.classList.add("page-container-grouped-styles"); // wrapper.vm.$options.methods.loadInitialData = jest.fn().mockImplementation(() => {
document.body.appendChild(container); // return new Promise((resolve) => {
// resolve(mockNewShopList);
// });
// });
const { wrapper } = setupMocks({ // // Act
mixins: [mockMixin], // wrapper.vm.initializeComponent(shopQuestionInitialData);
props: {
modelValue: "127",
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
},
mountOptions: {
attachTo: document.body,
},
});
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn(); // await wrapper.vm.$options.watch.serviceZipCode.handler.call(wrapper.vm, "43054");
wrapper.vm.shops = shopQuestionInitialData;
await wrapper.vm.$nextTick(); // // Assert
// expect(wrapper.vm.shopProviders.length).toEqual(3);
// expect(wrapper.vm.shopProviders).toEqual(mockNewShopList.shopProviders);
// });
wrapper.vm.$options.methods.loadInitialData = jest.fn().mockImplementation(() => { // it("Should clear the existing answers when the service zip code changes", async () => {
return new Promise((resolve) => { // // Arrange
resolve(newShopList); // const { wrapper } = setupMocks({
}); // mixins: [mockMixin],
}); // props: {
await wrapper.vm.$options.watch.serviceZipCode.handler.call(wrapper.vm, "43054"); // modelValue: {
// address: {
// city: "POWELL",
// country: "US",
// state: "OH",
// streetAddress: "3938 POWELL RD",
// zipCode: "43065",
// },
// distanceInMiles: 16.2690495685233,
// providerNumber: "003341",
// },
// serviceZipCode: "43081",
// selectedAppointmentType: "Dropoff",
// cmsWidgetName: cmsWidgetName,
// },
// mountOptions: {
// attachTo: document.body,
// },
// });
await wrapper.vm.$nextTick(); // //Act
// wrapper.vm.initializeComponent(shopQuestionInitialData);
// Assert // wrapper.setProps({
expect(wrapper.vm.shops.length).toEqual(3); // selectedAppointmentType: "Inshop",
expect(wrapper.vm.shops).toEqual(mockNewShopList.data); // });
});
it("Should clear the existing answers when the service zip code changes", async () => { // await wrapper.vm.$nextTick();
// Arrange/Act
const container = document.createElement("div");
container.scrollTo = jest.fn();
container.classList.add("page-container-grouped-styles"); // // Assert
document.body.appendChild(container); // expect(wrapper.vm.answers.length).toEqual(3);
// });
const { wrapper } = setupMocks({
mixins: [mockMixin],
props: {
modelValue: "127",
serviceZipCode: "43081",
selectedAppointmentType: "Dropoff",
cmsWidgetName: cmsWidgetName,
},
mountOptions: {
attachTo: document.body,
},
});
wrapper.vm.$refs.buttonQuestion.resetField = jest.fn();
wrapper.vm.initializeComponent(shopQuestionInitialData);
wrapper.setProps({
selectedAppointmentType: "Inshop",
});
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.answers.length).toEqual(3);
});
}); });
function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) { function setupMocks({ mountOptions, mixins, props, isShallowMount = true }) {

View file

@ -1,6 +1,6 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div class="shop-question" aria-live="polite"> <div v-if="isDisplayed" class="shop-question" aria-live="polite">
<alert <alert
ref="alertDropoffInformation" ref="alertDropoffInformation"
v-if="displayDropoffInformation" v-if="displayDropoffInformation"
@ -21,7 +21,7 @@
isRequired isRequired
validationRules="option-required" /> validationRules="option-required" />
<textLink <textLink
v-if="displaySeeMoreLocationsLink" v-show="displaySeeMoreLocationsLink"
ref="showMoreShopsLink" ref="showMoreShopsLink"
class="show-more-shops-link" class="show-more-shops-link"
id="showMoreShopsId" id="showMoreShopsId"
@ -49,6 +49,7 @@ import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules"; import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages"; import { errorMessages } from "@/constants/error-messages";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { nextTick } from "vue";
defineRule("option-required", required(errorMessages.OPTION_REQUIRED)); defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
@ -57,7 +58,7 @@ export default {
mixins: [baseMixin], mixins: [baseMixin],
data() { data() {
return { return {
shops: [], shopProviders: [],
shopListButton: shopListButton, shopListButton: shopListButton,
answers: [], answers: [],
shopIndex: 0, shopIndex: 0,
@ -65,11 +66,14 @@ export default {
}; };
}, },
props: { props: {
modelValue: String, modelValue: {
type: Object,
},
serviceZipCode: String, serviceZipCode: String,
selectedAppointmentType: String, selectedAppointmentType: String,
cmsWidgetName: String, cmsWidgetName: String,
validationRules: String, validationRules: String,
isDisplayed: Boolean,
}, },
computed: { computed: {
questionText() { questionText() {
@ -80,7 +84,13 @@ export default {
return this.modelValue; return this.modelValue;
}, },
set: function (newValue) { set: function (newValue) {
// Button Question only supports primitive values so we must get the full object to emit to the page
const provider = this.shopProviders?.find(
(provider) => provider.providerNumber == newValue
);
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);
this.$emit("providerSelected", provider);
}, },
}, },
displayDropoffInformation() { displayDropoffInformation() {
@ -91,13 +101,16 @@ export default {
}, },
}, },
methods: { methods: {
loadInitialData() { loadInitialData(serviceZipCode) {
return baseMixin.methods.dispatchStoreAction(storeActions.GET_PROVIDER_LOCATIONS, { return this.loadData(serviceZipCode);
serviceZipCode: this.serviceZipCode, },
loadData(serviceZipCode) {
return baseMixin.methods.dispatchStoreAction(storeActions.GET_PROVIDERS, {
serviceZipCode: serviceZipCode,
}); });
}, },
initializeComponent(shopQuestionInitialData) { initializeComponent(shopQuestionInitialData) {
this.shops = shopQuestionInitialData; this.shopProviders = shopQuestionInitialData.shopProviders;
}, },
async getNextShopsFromList(numberToGet = 3) { async getNextShopsFromList(numberToGet = 3) {
const shopIterator = (array, n) => { const shopIterator = (array, n) => {
@ -105,20 +118,32 @@ export default {
return () => { return () => {
const end = this.shopIndex + n; const end = this.shopIndex + n;
const part = array.slice(this.shopIndex, end); const part = array.slice(this.shopIndex, end);
this.shopIndex = end < l ? end : this.shops.length; this.shopIndex = end < l ? end : this.shopProviders.length;
return part; return part;
}; };
}; };
const nextShop = shopIterator(this.shops, numberToGet); const toTitleCase = (str) => {
return str.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
const nextShop = shopIterator(this.shopProviders, numberToGet);
// Map API result data // Map API result data
const mappedData = nextShop().map((shop) => { const mappedData = nextShop().map((shopProvider) => {
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
const city = toTitleCase(shopProvider.address.city);
const state = shopProvider.address.state;
const zipCode = shopProvider.address.zipCode;
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
return { return {
Name: shop.providerNumber, buttonLabel: streetAddress,
buttonLabel: shop.city, buttonLabelSubCopy: `${distanceInMiles} mi`,
buttonLabelSubCopy: `${shop.distance} mi`, buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
buttonBodyCopy: `${shop.streetAddress}, ${shop.city}, ${shop.state} ${shop.zipCode}`, value: shopProvider.providerNumber,
}; };
}); });
@ -132,7 +157,7 @@ export default {
await this.$nextTick(); await this.$nextTick();
if (this.shopIndex == this.shops.length) { if (this.shopIndex == this.shopProviders.length) {
this.displaySeeMoreLocationsLink = false; this.displaySeeMoreLocationsLink = false;
} else { } else {
this.displaySeeMoreLocationsLink = true; this.displaySeeMoreLocationsLink = true;
@ -142,48 +167,53 @@ export default {
this.scrollToPageBottom(); this.scrollToPageBottom();
}, },
resetShopList() { resetAnswers() {
this.answers = []; this.answers = [];
this.shopIndex = 0; this.shopIndex = 0;
this.selectedValue = ""; this.selectedValue = "";
this.$refs.buttonQuestion.resetField();
if (this.$refs.buttonQuestion) {
this.$refs.buttonQuestion.resetField();
}
}, },
async reloadShopData(serviceZipCode) { async reloadShopData(serviceZipCode) {
const result = await this.loadInitialData(serviceZipCode); const result = await this.loadData(serviceZipCode);
this.initializeComponent(result.data); this.initializeComponent(result.data);
this.resetShopList();
this.resetAnswers();
await nextTick();
await this.getNextShopsFromList();
}, },
}, },
watch: { watch: {
serviceZipCode: {
async handler(newValue) {
await this.reloadShopData(newValue);
},
},
selectedAppointmentType: { selectedAppointmentType: {
async handler(newValue) { async handler(newValue) {
// If the validation has been previously triggered, clear it before displaying the component this.resetAnswers();
this.$refs.buttonQuestion.resetField();
this.resetShopList();
await this.getNextShopsFromList();
await this.$nextTick(); await this.$nextTick();
this.scrollToPageBottom(); if (newValue !== "Mobile") {
await this.getNextShopsFromList();
}
}, },
}, },
shops: { shopProviders: {
handler(newValue) { async handler(newValue) {
this.$refs.buttonQuestion.resetField(); //this.resetAnswers();
const selectedShopIndex = newValue.findIndex(
(provider) => provider.providerNumber == this.modelValue
);
if (selectedShopIndex >= 3) { await this.$nextTick();
this.getNextShopsFromList(selectedShopIndex + 1);
} else { if (this.selectedAppointmentType) {
this.getNextShopsFromList(); const selectedShopIndex = newValue.findIndex(
(provider) => provider.providerNumber == this.modelValue
);
if (selectedShopIndex >= 3) {
await this.getNextShopsFromList(selectedShopIndex + 1);
} else {
await this.getNextShopsFromList();
}
} }
}, },
}, },

View file

@ -32,6 +32,7 @@ import { applicationConfig } from "../constants/application-config";
import datePicker from "@/digital-components/date-picker/date-picker.vue"; import datePicker from "@/digital-components/date-picker/date-picker.vue";
import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue"; import demoDatePicker from "@/layouts/demo-date-picker/demo-date-picker.vue";
import customerDetails from "@/layouts/customer-details/customer-details.vue"; import customerDetails from "@/layouts/customer-details/customer-details.vue";
import review from "@/layouts/review/review";
const routes = [ const routes = [
{ {
@ -49,6 +50,11 @@ const routes = [
name: "date-picker", name: "date-picker",
component: datePicker, component: datePicker,
}, },
{
path: "/review", // This is a temporary route for testing.
name: "review",
component: review,
},
{ {
path: "/", path: "/",
name: "root", name: "root",

View file

@ -44,6 +44,13 @@ const getDefaultState = () => {
zipCode: null, zipCode: null,
zipCodeCtu: null, zipCodeCtu: null,
appointmentType: null, appointmentType: null,
provider: {
providerNumber: null,
address: null,
city: null,
state: null,
zip: null,
},
}, },
customer: { customer: {
emailAddress: null, emailAddress: null,
@ -226,10 +233,22 @@ export const mutations = {
}, },
updateServiceLocation(state, serviceLocationInfo) { updateServiceLocation(state, serviceLocationInfo) {
state.order.serviceLocation.address = serviceLocationInfo.address; state.order.serviceLocation.address = serviceLocationInfo.address;
state.order.serviceLocation.address2 = serviceLocationInfo.address2;
state.order.serviceLocation.city = serviceLocationInfo.city; state.order.serviceLocation.city = serviceLocationInfo.city;
state.order.serviceLocation.state = serviceLocationInfo.state; state.order.serviceLocation.state = serviceLocationInfo.state;
state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode; state.order.serviceLocation.zipCode = serviceLocationInfo.zipCode;
state.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu; state.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu;
state.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType;
state.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected;
if (serviceLocationInfo.provider) {
state.order.serviceLocation.provider.providerNumber =
serviceLocationInfo.provider?.providerNumber;
state.order.serviceLocation.provider.address = serviceLocationInfo.provider?.address;
state.order.serviceLocation.provider.city = serviceLocationInfo.provider?.city;
state.order.serviceLocation.provider.state = serviceLocationInfo.provider?.state;
state.order.serviceLocation.provider.zip = serviceLocationInfo.provider?.zip;
}
}, },
// applicationUser MUTATIONS // applicationUser MUTATIONS
@ -358,15 +377,31 @@ export const mutations = {
state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData; state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData;
state.order.payment.parentAccountNumber = state.order.payment.parentAccountNumber =
sessionInformation.order.payment.parentAccountNumber; sessionInformation.order.payment.parentAccountNumber;
state.order.providerNumber = sessionInformation.order.providerNumber;
(state.order.serviceLocation.address = (state.order.serviceLocation.address =
sessionInformation.order.serviceLocation.streetAddress), sessionInformation.order.serviceLocation.streetAddress),
(state.order.serviceLocation.address2 =
sessionInformation.order.serviceLocation.address2),
(state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city), (state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city),
(state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state), (state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state),
(state.order.serviceLocation.zipCode = (state.order.serviceLocation.zipCode =
sessionInformation.order.serviceLocation.zipCode), sessionInformation.order.serviceLocation.zipCode),
(state.order.serviceLocation.zipCodeCtu = (state.order.serviceLocation.zipCodeCtu =
sessionInformation.order.serviceLocation.zipCodeCtu); sessionInformation.order.serviceLocation.zipCodeCtu);
state.order.serviceLocation.appointmentType =
sessionInformation.order.serviceLocation.appointmentType;
state.order.serviceLocation.isVehicleProtected =
sessionInformation.order.serviceLocation.isVehicleProtected;
state.order.serviceLocation.provider.providerNumber =
sessionInformation.order.serviceLocation.provider?.providerNumber;
state.order.serviceLocation.provider.address =
sessionInformation.order.serviceLocation.provider?.address;
state.order.serviceLocation.provider.city =
sessionInformation.order.serviceLocation.provider?.city;
state.order.serviceLocation.provider.state =
sessionInformation.order.serviceLocation.provider?.state;
state.order.serviceLocation.provider.zip =
sessionInformation.order.serviceLocation.provider?.zip;
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance; state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
state.order.payment.insuranceCoverage.isVerified = state.order.payment.insuranceCoverage.isVerified =
@ -681,6 +716,15 @@ export const actions = {
}); });
}, },
// Location API Actions
getAlertReasonsByCtu(context, { ctu }) {
return globalMethods.callHttpClient({
method: endpoints.GetAlertReasons.method,
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
payload: {},
});
},
// Misc Actions // Misc Actions
updateStoreWithSaveSessionResponse( updateStoreWithSaveSessionResponse(
context, context,
@ -970,21 +1014,34 @@ export const actions = {
partNumber: lineItem.partNumber, partNumber: lineItem.partNumber,
}) })
); );
const lineItemsToSend = buildQueryStringParameterFromArrayOfComplexObjects( const lineItems = buildQueryStringParameterFromArrayOfComplexObjects(
lineItemsWithOnlyPartNumbers, lineItemsWithOnlyPartNumbers,
"lineItems" "lineItems"
); );
const vehicle = context.getters.vehicle;
const carId = vehicle.carId;
const damage = context.getters.damage;
const glassArray = convertGlassPieceNamingForApi(damage.glassToReplace);
const glassPieces = buildQueryStringParameterFromArrayOfComplexObjects(
glassArray,
"glassPieces"
);
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method, method: endpoints.GetServiceabilityDetails.method,
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&${lineItemsToSend}`, endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
}); });
}, },
getProviderLocations(context, { serviceZipCode }) { getProviders(context, { serviceZipCode }) {
return globalMethods.callMockHttpClient({ const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
method: endpoints.GetProviderLocations.method, const shopRadiusInMiles = 100;
//TODO: Remove Mocky Endpoints
endpoint: "https://run.mocky.io/v3/abf2fa63-8287-4e46-b169-f5bec65c8dff", return globalMethods.callHttpClient({
method: endpoints.GetProviders.method,
endpoint: `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}`,
}); });
}, },
@ -1098,13 +1155,22 @@ export const actions = {
isInsurance: order.payment.isInsurance ?? false, isInsurance: order.payment.isInsurance ?? false,
parentAccountNumber: order.payment.parentAccountNumber, parentAccountNumber: order.payment.parentAccountNumber,
}, },
providerNumber: "",
serviceLocation: { serviceLocation: {
streetAddress: order.serviceLocation.address, streetAddress: order.serviceLocation.address,
streetAddress2: order.serviceLocation.address2,
city: order.serviceLocation.city, city: order.serviceLocation.city,
state: order.serviceLocation.state, state: order.serviceLocation.state,
zipCode: order.serviceLocation.zipCode, zipCode: order.serviceLocation.zipCode,
zipCodeCtu: order.serviceLocation.zipCodeCtu, zipCodeCtu: order.serviceLocation.zipCodeCtu,
appointmentType: order.serviceLocation.appointmentType,
isVehicleProtected: order.serviceLocation.isVehicleProtected,
provider: {
providerNumber: order.serviceLocation.provider?.providerNumber,
address: order.serviceLocation.provider?.address,
city: order.serviceLocation.provider?.city,
state: order.serviceLocation.provider?.state,
zip: order.serviceLocation.provider?.zip,
},
}, },
existingPromoCode: null, existingPromoCode: null,
referralCorrelationId: order.referralCorrelationId, referralCorrelationId: order.referralCorrelationId,