Merge pull request #255 from Safelite/feature/digital/SSR-351
Feature/digital/ssr 351
This commit is contained in:
commit
8e9bc4bc5b
9 changed files with 734 additions and 25 deletions
|
|
@ -62,6 +62,10 @@ const endpoints = {
|
|||
GetSupportingItems: {
|
||||
url: '/parts/api/v1/parts/supporting-items',
|
||||
method: 'POST'
|
||||
},
|
||||
GetServiceabilityDetails: {
|
||||
url: "/location/api/v1/location/serviceability-details",
|
||||
method: "GET",
|
||||
},
|
||||
GetVehicle: {
|
||||
url: '/vehicle/api/v1/vehicle/lookup',
|
||||
|
|
|
|||
26
src/helpers/service-location-helper.js
Normal file
26
src/helpers/service-location-helper.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useMainStore } from '@/store';
|
||||
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||
const serviceabilityDetails = await useMainStore().getServiceabilityDetails(
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
lineItems: lineItems,
|
||||
},
|
||||
false
|
||||
);
|
||||
return Promise.resolve(serviceabilityDetails);
|
||||
}
|
||||
|
||||
export async function getZipCodeData(zipCode) {
|
||||
const serviceZipValidationResponse = await useMainStore().validateZip(
|
||||
{ zip: zipCode }
|
||||
);
|
||||
|
||||
return {
|
||||
containsMilitaryBase: serviceZipValidationResponse.data.containsMilitaryBase,
|
||||
isValid: serviceZipValidationResponse.data.isValid,
|
||||
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
||||
state: serviceZipValidationResponse.data.state,
|
||||
zipCodeCtu: serviceZipValidationResponse.data.zipCodeCtu,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,43 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import serviceLocationModal from "@/layouts/service-location/service-location.vue";
|
||||
import serviceLocation from "@/layouts/service-location/service-location.vue";
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
if (widgetName === linkWidgetName) {
|
||||
return mockLinkCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
if (widgetName === modalWidgetName) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
|
||||
getZipCodeData: jest.fn((zip) => {
|
||||
if (zip === "43235" || zip === "55555") {
|
||||
return Promise.resolve({
|
||||
containsMilitaryBase: false,
|
||||
isValid: true,
|
||||
state: "OH",
|
||||
zipCodeCtu: "01820",
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
containsMilitaryBase: false,
|
||||
isValid: false,
|
||||
state: null,
|
||||
zipCodeCtu: null,
|
||||
});
|
||||
}),
|
||||
|
||||
onSubmit: jest.fn(),
|
||||
onInvalidSubmit: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
describe("navigation", () => {
|
||||
test("if the back button is clicked, navigate back", async () => {
|
||||
|
|
@ -17,16 +54,62 @@ describe("navigation", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("updating service zip", () => {
|
||||
test("updates the page model after changing the service zip code", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
const newServiceZipCodeQuestion = {
|
||||
zipCode: "61606",
|
||||
state: "IL",
|
||||
};
|
||||
|
||||
const serviceZipCodeComponent = wrapper.findComponent({
|
||||
ref: "serviceZipCodeQuestion",
|
||||
});
|
||||
|
||||
// Act
|
||||
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
|
||||
});
|
||||
|
||||
test("resets appointment type selection when service zip code is updated by service zip modal", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
const serviceZipCodeComponent = wrapper.findComponent({
|
||||
ref: "serviceZipCodeQuestion",
|
||||
});
|
||||
|
||||
const newServiceZipCodeQuestion = {
|
||||
zipCode: "61606",
|
||||
state: "IL",
|
||||
};
|
||||
|
||||
wrapper.vm.selectedAppointmentType = "Inshop";
|
||||
|
||||
// Act
|
||||
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.selectedAppointmentType).toStrictEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks()
|
||||
{
|
||||
const wrapper = shallowMount(
|
||||
serviceLocationModal,
|
||||
serviceLocation,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -3,19 +3,24 @@
|
|||
<div class="page-container-grouped-styles">
|
||||
<div class="fade-on-route-transition position-relative">
|
||||
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" id="sub-header" />
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row px-3">
|
||||
<div class="col">
|
||||
<div class="select-car-form rounded">
|
||||
<serviceZipModalQuestion
|
||||
v-model="serviceZipCodeQuestion"
|
||||
ref="serviceZipCodeQuestion"
|
||||
modalWidgetName="ServiceZipModalWidget" />
|
||||
<buttonQuestion
|
||||
cmsWidgetName="ServiceTypeQuestionWidget"
|
||||
:questionText="questionText"
|
||||
:answers="answersFromCms"
|
||||
buttonTypeString="listCard"
|
||||
isRequired
|
||||
v-model="selectedValues"
|
||||
validationRules="selection-required"/>
|
||||
cmsWidgetName="ServiceTypeQuestionWidget"
|
||||
:questionText="questionText"
|
||||
:answers="answersFromCms"
|
||||
buttonTypeString="listCard"
|
||||
isRequired
|
||||
v-model="selectedAppointmentType"
|
||||
validationRules="selection-required"/>
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row px-3">
|
||||
|
|
@ -49,41 +54,66 @@ import { settleAllPromises } from "@/helpers/layout-helper";
|
|||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import { useMainStore } from "@/store";
|
||||
import { getServiceabilityDetails, getZipCodeData } from "@/helpers/service-location-helper";
|
||||
|
||||
// Import Component
|
||||
import baseFormMixin from "@/mixins/base-form-mixin";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
|
||||
import siteHeader from "@/iss-components/site-header/site-header.vue";
|
||||
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
|
||||
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("selection-required", required(errorMessages.OPTION_REQUIRED));
|
||||
export default {
|
||||
name: "service-location",
|
||||
mixins: [baseFormMixin],
|
||||
components: {
|
||||
siteFooter,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
buttonQuestion,
|
||||
Form,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mixins: [baseFormMixin, baseMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||
|
||||
const serviceZipCode = useMainStore().order.customer.address.zipCode;
|
||||
const zipCodeData = getZipCodeData(serviceZipCode);
|
||||
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
}, ];
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "serviceabilityDetails",
|
||||
promise: serviceabilityDetailsPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "zipCodeData",
|
||||
promise: zipCodeData,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
setup() {
|
||||
const mainStore = useMainStore();
|
||||
return { mainStore };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zipCode: this.mainStore.order.customer.address.zipCode,
|
||||
state: this.mainStore.order.customer.address.state,
|
||||
isServiceZipServiceable: null,
|
||||
selectedAppointmentType: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent("ServiceTypeQuestionWidget", "QuestionText");
|
||||
|
|
@ -91,6 +121,24 @@ export default {
|
|||
answersFromCms() {
|
||||
return this.getCmsContent("ServiceTypeQuestionWidget", "Answers");
|
||||
},
|
||||
serviceZipCodeQuestion: {
|
||||
get: function() {
|
||||
return {
|
||||
zipCode: this.zipCode,
|
||||
state: this.state,
|
||||
};
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue.zipCode !== this.zipCode) {
|
||||
this.selectedAppointmentType = null;
|
||||
}
|
||||
|
||||
this.zipCode = newValue.zipCode;
|
||||
this.state = newValue.state;
|
||||
|
||||
this.$nextTick();
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisiteValid() {
|
||||
|
|
@ -105,6 +153,14 @@ export default {
|
|||
async forwardButtonAction() {},
|
||||
resetDependentState() {},
|
||||
},
|
||||
components: {
|
||||
siteFooter,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
buttonQuestion,
|
||||
Form,
|
||||
serviceZipModalQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
import { mount, shallowMount } from "@vue/test-utils";
|
||||
import serviceZipModalQuestion from "./service-zip-modal-question";
|
||||
import crypto from "crypto";
|
||||
global.crypto = crypto;
|
||||
|
||||
jest.mock("@/digital-components/textbox-question/textbox-question", () => ({
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return widgetName[cmsFieldName];
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("@/digital-components/modal/modal", () => ({
|
||||
methods: {
|
||||
closeModal: jest.fn(),
|
||||
resetButtonStyle: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockGetServiceabilityDetails = (mockServiceZipCode) => {
|
||||
const serviceabilityDetails = {
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
};
|
||||
|
||||
return Promise.resolve(serviceabilityDetails);
|
||||
};
|
||||
|
||||
const mockGetZipCodeData = (mockServiceZipCode) => {
|
||||
if (mockServiceZipCode === "43235") {
|
||||
return {
|
||||
containsMilitaryBase: false,
|
||||
isValid: true,
|
||||
state: "OH",
|
||||
zipCodeCtu: "01820",
|
||||
};
|
||||
}
|
||||
|
||||
if (mockServiceZipCode === "61606") {
|
||||
return {
|
||||
containsMilitaryBase: false,
|
||||
isValid: true,
|
||||
state: "IL",
|
||||
zipCodeCtu: "01526",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
containsMilitaryBase: false,
|
||||
isValid: false,
|
||||
state: null,
|
||||
zipCodeCtu: null,
|
||||
};
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
"@/helpers/service-location-helper",
|
||||
() => ({
|
||||
getServiceabilityDetails: jest.fn((mockServiceZipCode) => {
|
||||
return mockGetServiceabilityDetails(mockServiceZipCode);
|
||||
}),
|
||||
getZipCodeData: jest.fn((mockServiceZipCode) => {
|
||||
return mockGetZipCodeData(mockServiceZipCode);
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const linkWidgetName = "linkWidgetName";
|
||||
const modalWidgetName = "modalWidgetName";
|
||||
|
||||
const mockLinkCmsContent = {
|
||||
BodyText: "Sample link body text here.",
|
||||
};
|
||||
|
||||
const mockModalCmsContent = {
|
||||
FooterText: "Sample modal footer text here.",
|
||||
};
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
if (widgetName === linkWidgetName) {
|
||||
return mockLinkCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
if (widgetName === modalWidgetName) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
describe("service-zip-modal-question.vue", () => {
|
||||
it("Initial state on load with existing location info", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "OH",
|
||||
zipCode: "43235",
|
||||
};
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
linkWidgetName: linkWidgetName,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.html()).not.toEqual(expect.stringContaining(mockLinkCmsContent["BodyText"]));
|
||||
});
|
||||
|
||||
it("Should emit update:modelValue on setZipCode for a valid, serviceable zip", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "IL",
|
||||
zipCode: "61606",
|
||||
};
|
||||
|
||||
const zip = "43235";
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
mobileFeePart: {},
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.internalModel.zipCode = zip;
|
||||
|
||||
await wrapper.vm.setZipCode();
|
||||
|
||||
let expectedEmit = [[{ state: "OH", zipCode: "43235" }]];
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("update:modelValue")).toEqual(expectedEmit);
|
||||
});
|
||||
|
||||
it("Should not emit update:modelValue on setZipCode for an invalid zip", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "IL",
|
||||
zipCode: "61606",
|
||||
};
|
||||
|
||||
const zip = "";
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.internalModel.zipCode = zip;
|
||||
await wrapper.vm.setZipCode();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("update:modelValue")).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it("Should display invalid zip alerts for invalid ip inputs", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "IL",
|
||||
zipCode: "61606",
|
||||
};
|
||||
|
||||
const zip = "11111";
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.internalModel.zipCode = zip;
|
||||
await wrapper.vm.setZipCode();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInvalidZipAlert).toBe(true);
|
||||
});
|
||||
|
||||
it("Should reset all alerts on onModalClosed", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "IL",
|
||||
zipCode: "61606",
|
||||
};
|
||||
|
||||
const zip = "";
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.internalModel.zipCode = zip;
|
||||
await wrapper.vm.setZipCode();
|
||||
wrapper.vm.onModalClosed();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.displayInvalidZipAlert).toBe(false);
|
||||
});
|
||||
|
||||
it("Should prepopulate the zip on onModalOpened", async () => {
|
||||
// Arrange
|
||||
let serviceZipCodeQuestion = {
|
||||
state: "IL",
|
||||
zipCode: "61606",
|
||||
};
|
||||
|
||||
const zip = "123";
|
||||
|
||||
const wrapper = mount(serviceZipModalQuestion, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
modelValue: serviceZipCodeQuestion,
|
||||
modalWidgetName: modalWidgetName,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.internalModel.zipCode = zip;
|
||||
wrapper.vm.onModalOpened();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.internalModel.zipCode).toEqual("61606");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
<template>
|
||||
<div class="text-center mt-1">
|
||||
<div class="update-zip-text-link">
|
||||
<textLink
|
||||
id="serviceZipLinkPromptId"
|
||||
linkType="text"
|
||||
:text="serviceZipLinkText"
|
||||
href="#!"
|
||||
@click-event="openModal"
|
||||
aria-label="Modal window" />
|
||||
</div>
|
||||
</div>
|
||||
<modal
|
||||
:ref="modalName"
|
||||
:onModalOpenedCallback="onModalOpened"
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
:footerButtonText="modalFooterText"
|
||||
@footer-button-event="setZipCode" >
|
||||
<serviceZipQuestion
|
||||
ref="serviceZipCodeQuestion"
|
||||
customInputId="serviceZipCode"
|
||||
v-model="internalModel.zipCode"
|
||||
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
|
||||
:cmsWidgetName="textboxQuestionWidgetName" />
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
:cmsWidgetName="alertInvalidZipWidgetName"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import serviceZipQuestion from "@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question";
|
||||
import modal from "@/digital-components/modal/modal.vue"
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
||||
import { getServiceabilityDetails, getZipCodeData } from "@/helpers/service-location-helper";
|
||||
|
||||
export default {
|
||||
name: "service-zip-modal-question",
|
||||
emits: ["update:modelValue", "updated-serviceability", "updated-contains-military-base"],
|
||||
data() {
|
||||
return {
|
||||
internalModel: this.copyModel(this.modelValue),
|
||||
serviceZipCodeTextInputId: "",
|
||||
displayInvalidZipAlert: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
state: "",
|
||||
zipCode: "",
|
||||
})
|
||||
},
|
||||
modalWidgetName: String,
|
||||
},
|
||||
setup() {
|
||||
const textboxQuestionWidgetName = "ServiceZipQuestionWidget";
|
||||
const alertInvalidZipWidgetName = "AlertInvalidZipWidget";
|
||||
return {
|
||||
textboxQuestionWidgetName,
|
||||
alertInvalidZipWidgetName,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
serviceZipLinkText() {
|
||||
if (this.modelValue.zipCode && this.modelValue.zipCode.length > 0) {
|
||||
return this.modelValue.state + ", " + this.modelValue.zipCode;
|
||||
}
|
||||
},
|
||||
modalHeaderText() {
|
||||
return this.getCmsContent(this.textboxQuestionWidgetName, "QuestionText");
|
||||
},
|
||||
modalFooterText() {
|
||||
return this.getCmsContent(this.modalWidgetName, "FooterText");
|
||||
},
|
||||
modalName() {
|
||||
return this.modalWidgetName;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetAlerts() {
|
||||
this.displayInvalidZipAlert = false;
|
||||
},
|
||||
resetsOnZipInput() {
|
||||
this.resetAlerts();
|
||||
},
|
||||
focusOnZipInput() {
|
||||
const input = document.getElementById(this.serviceZipCodeTextInputId);
|
||||
input?.focus();
|
||||
},
|
||||
copyModel(modelToCopy) {
|
||||
return {
|
||||
state: modelToCopy.state,
|
||||
zipCode: modelToCopy.zipCode,
|
||||
};
|
||||
},
|
||||
openModal() {
|
||||
this.$refs[this.modalName].openModal();
|
||||
},
|
||||
closeModal() {
|
||||
this.$refs[this.modalName].closeModal();
|
||||
},
|
||||
resetModalButtonStyle() {
|
||||
this.$refs[this.modalName].resetButtonStyle();
|
||||
},
|
||||
onInputIdAssigned(inputId) {
|
||||
this.serviceZipCodeTextInputId = inputId;
|
||||
},
|
||||
onModalOpened() {
|
||||
this.internalModel.zipCode = this.modelValue.zipCode;
|
||||
this.focusOnZipInput();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.internalModel.zipCode = this.modelValue.zipCode;
|
||||
this.resetsOnZipInput();
|
||||
},
|
||||
async setZipCode() {
|
||||
if (this.internalModel.zipCode !== this.modelValue.zipCode) {
|
||||
this.resetAlerts();
|
||||
|
||||
const zipCodeData = await getZipCodeData(this.internalModel.zipCode);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
this.focusOnZipInput();
|
||||
this.resetModalButtonStyle();
|
||||
} else {
|
||||
this.internalModel.state = zipCodeData.state;
|
||||
const serviceZipCode = this.internalModel.zipCode;
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-serviceability", serviceabilityDetails.data);
|
||||
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
|
||||
|
||||
// Update the page level model
|
||||
this.$emit("update:modelValue", this.internalModel);
|
||||
this.closeModal();
|
||||
}
|
||||
} else {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
getZipCodeData,
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
this.internalModel = this.copyModel(newValue);
|
||||
},
|
||||
"internalModel.zipCode": {
|
||||
handler() {
|
||||
this.resetsOnZipInput();
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textLink,
|
||||
modal,
|
||||
serviceZipQuestion,
|
||||
alert
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
a#serviceZipLinkPromptId {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.update-zip-text-link::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 13px;
|
||||
height: 16px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.49635 1.00142e-07C5.64734 -0.000153295 4.80722 0.175918 4.0274 0.517444C3.24757 0.858969 2.54443 1.35877 1.96099 1.98626C0.765713 3.27588 0.0999756 4.98141 0.0999756 6.75394C0.0999756 8.52646 0.765713 10.232 1.96099 11.5216L5.98324 15.777C6.04954 15.8475 6.12918 15.9036 6.21736 15.9419C6.30555 15.9802 6.40045 16 6.49635 16C6.59225 16 6.68716 15.9802 6.77534 15.9419C6.86353 15.9036 6.94317 15.8475 7.00946 15.777L11.0317 11.52C12.2391 10.2383 12.909 8.52914 12.8999 6.75394C12.9094 4.97818 12.2394 3.26832 11.0317 1.98626C10.4481 1.35899 9.74493 0.859347 8.96514 0.517839C8.18535 0.176331 7.34532 0.000130509 6.49635 1.00142e-07V1.00142e-07ZM6.49635 9.13131C6.02507 9.13131 5.56437 8.98913 5.17251 8.72275C4.78065 8.45637 4.47524 8.07776 4.29488 7.63479C4.11453 7.19181 4.06734 6.70438 4.15928 6.23412C4.25123 5.76387 4.47817 5.33191 4.81142 4.99287C5.14467 4.65384 5.56925 4.42295 6.03148 4.32941C6.49371 4.23587 6.97282 4.28388 7.40823 4.46736C7.84364 4.65085 8.21579 4.96157 8.47762 5.36023C8.73945 5.7589 8.87921 6.2276 8.87921 6.70707C8.87921 7.34974 8.62837 7.96611 8.18185 8.4207C7.73532 8.87528 7.12964 9.13088 6.49794 9.13131H6.49635Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import serviceZipQuestion from "./service-zip-question";
|
||||
|
||||
describe("service-zip-question.vue", () => {
|
||||
it("Should get the modelValue", async () => {
|
||||
// Arrange
|
||||
const text = "test";
|
||||
const wrapper = shallowMount(serviceZipQuestion, {
|
||||
props: {
|
||||
modelValue: text,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
const modelValueText = wrapper.vm.value;
|
||||
wrapper.vm.value = "test also";
|
||||
|
||||
// Assert
|
||||
expect(modelValueText).toEqual("test");
|
||||
});
|
||||
|
||||
it("Should emit to set value", async () => {
|
||||
// Arrange
|
||||
const text = "test";
|
||||
const wrapper = shallowMount(serviceZipQuestion, {
|
||||
props: {
|
||||
modelValue: text,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
|
||||
// Act
|
||||
const modelValueText = wrapper.vm.value;
|
||||
wrapper.vm.value = "test also";
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("update:modelValue")).toEqual([["test also"]]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<textboxQuestion
|
||||
ref="zipInputTextQuestion"
|
||||
:cmsWidgetName="cmsWidgetName"
|
||||
v-model="value"
|
||||
inputId="serviceZipCode"
|
||||
questionAlignment="center"
|
||||
cornerStyle="rounded"
|
||||
mask="#####"
|
||||
:displayQuestionText="false"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
|
||||
//Supporting Files
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required, regex } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
// Define Validation Rules
|
||||
defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
|
||||
defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
|
||||
|
||||
export default {
|
||||
name: "service-zip-question",
|
||||
props: {
|
||||
modelValue: {
|
||||
serviceZipCode: String,
|
||||
},
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textboxQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -451,7 +451,7 @@ export const useMainStore = defineStore({
|
|||
console.error(error);
|
||||
return [];
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
async getRainDefense() {
|
||||
return globalMethods
|
||||
|
|
@ -525,6 +525,16 @@ export const useMainStore = defineStore({
|
|||
return availableLineItems;
|
||||
},
|
||||
|
||||
|
||||
getServiceabilityDetails({ serviceZipCode }) {
|
||||
const lineItemsToSend = this.order.lineItems.supportingItems;
|
||||
const encodedLineItems = encodeURIComponent(JSON.stringify(lineItemsToSend));
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetServiceabilityDetails.method,
|
||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&lineItems=${encodedLineItems}`,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVehicleByVin(vin) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVehicleByVin.method,
|
||||
|
|
|
|||
Loading…
Reference in a new issue