diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 2760d6a1a..e6fa6d0ff 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -76,7 +76,7 @@ stages:
deployFolder: ''
region: us-east-1
appDeployVariables:
- __VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__)
+ __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
indexDeployVariables:
@@ -92,7 +92,7 @@ stages:
jobs:
- deployment: qaBuildDeployment
displayName: Build and Deploy FMG - QA
- environment: digitalCloud-qa
+ environment: NoApproval-All
container: node
workspace:
clean: all
@@ -116,7 +116,7 @@ stages:
deployFolder: ''
region: us-east-1
appDeployVariables:
- __VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__)
+ __VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
cfDistributionId: $(cfDistributionId)
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index d010bda25..e75fc82ab 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -21,14 +21,15 @@ module.exports = {
"!src/layouts/part-questions/**/*.vue",
"!src/layouts/reveal/**/*.vue",
"!src/layouts/estimate/**/*.vue",
- // REMOVE THESE AFTER WRITING UNIT TESTS
+ // REMOVE THESE AFTER WRITING UNIT TESTS
"!src/layouts/address-lookup/address-lookup.vue",
"!src/layouts/address-lookup/customer-questions/customer-questions.vue",
"!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue",
- "!src/common-components/dropdown-question/dropdown-question.vue",
- "!src/common-components/textbox-question/textbox-question.vue",
+ "!src/common-components/dropdown-question/dropdown-question.vue",
+ "!src/common-components/textbox-question/textbox-question.vue",
"!src/helpers/validation-rules.js",
- // END
+ "!src/helpers/damage-helper.js",
+ // END
], //! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
diff --git a/src/common-components/dropdown-question/dropdown-question.spec.js1 b/src/common-components/dropdown-question/dropdown-question.spec.js1
index 3eeb3662b..81669f714 100644
--- a/src/common-components/dropdown-question/dropdown-question.spec.js1
+++ b/src/common-components/dropdown-question/dropdown-question.spec.js1
@@ -1,6 +1,7 @@
import { shallowMount } from "@vue/test-utils";
import dropdownQuestion from "./dropdown-question";
import { nextTick } from "vue";
+import { maska } from 'maska';
describe("dropdownQuestion.vue", () => {
@@ -19,7 +20,7 @@ describe("dropdownQuestion.vue", () => {
expect(input.attributes()["aria-disabled"]).toEqual("true");
});
- it("Should render a text input", async () => {
+ it("Should render a select input", async () => {
// Act
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
@@ -49,33 +50,82 @@ describe("dropdownQuestion.vue", () => {
expect(input.attributes().id).toEqual("input ID");
});
-/* it("Should return label text", async () => {
+ it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {
+ // Arrange
+ //Mock CMS content
+ const questionText = "Question Text";
+ const cmsContent = {
+ QuestionText: questionText,
+ };
+
// Act
const wrapper = shallowMount(dropdownQuestion, {
- propsData: {
- labelText: "label text",
+ global: {
+ directives: {
+ maska: maska,
+ }
},
});
+ await wrapper.setData({
+ questionText: questionText,
+ });
+ wrapper.vm.initializeComponent(cmsContent);
// Assert
- const label = wrapper.find("label");
+ expect(wrapper.find("label").text()).toContain(questionText);
+ wrapper.unmount();
+ });
+
+ it("Should render the 'questionText' data value with '⁠' after the first character as the label text when disableAutoFill is true.", async () => {
+ // Arrange
+ //Mock CMS content
+ const originalQuestionText = "Question Text";
+ // Trust me, the below instance of the string "Question Text" actually has the ⁠ in it. You just can't see it
+ // Don't believe me? Copy it and paste it into Google. Then inspect the search field element in Dev Tools,
+ // you will see "Q&uestion Text"
+ const expectedQuestionText = "Question Text";
+ const cmsContent = {
+ QuestionText: originalQuestionText,
+ };
- expect(label.text()).toEqual("label text");
- }); */
-
- it("Should return input id", async () => {
// Act
const wrapper = shallowMount(dropdownQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
propsData: {
- inputId: "input ID",
+ disableAutoFill: true,
},
});
+ await wrapper.setData({
+ questionText: originalQuestionText,
+ });
+ wrapper.vm.initializeComponent(cmsContent);
// Assert
- const input = wrapper.find("select");
-
- // Expect
- expect(input.attributes().id).toEqual("input ID");
+ expect(wrapper.vm.$el.children[0].innerHTML).toBe(expectedQuestionText);
+ wrapper.unmount();
});
+ it("Should emit new value when modelValue is changed", async () => {
+ // Act
+ const wrapper = shallowMount(dropdownQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
+ propsData: {
+ modelValue: "val",
+ },
+ });
+
+ await wrapper.find("select").setValue("val2");
+
+ // Assert
+ expect(wrapper.emitted()).toHaveProperty('change')
+
+ });
});
diff --git a/src/common-components/dropdown-question/dropdown-question.vue b/src/common-components/dropdown-question/dropdown-question.vue
index 08ed368cf..7afdc0b38 100644
--- a/src/common-components/dropdown-question/dropdown-question.vue
+++ b/src/common-components/dropdown-question/dropdown-question.vue
@@ -37,6 +37,7 @@ export default {
isRequired: Boolean,
disableAutoFill: Boolean,
validationRules: String,
+ cmsWidgetName: String,
},
setup(props) {
const fieldOptions = {
@@ -57,18 +58,11 @@ export default {
handleChange,
meta,
};
- },
- data() {
- return {
- questionText: "",
- }
- },
- methods: {
- initializeComponent(cmsContent) {
- this.questionText = cmsContent;
- }
- },
+ },
computed: {
+ questionText(){
+ return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
+ },
selectedOption: {
get: function() {
return this.modelValue;
@@ -79,14 +73,22 @@ export default {
},
labelText: {
get: function () {
- let labelText = this.questionText;
+ const noBreakChar = "⁠";
+ var questionText = "";
if (this.disableAutoFill) {
- const noBreakChar = "⁠";
- const position = 1;
- labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
+ var words = this.questionText.toString().split(/[ ]+/);
+ words.forEach(function (word) {
+ const position = 1;
+ word = [word.toString().slice(0, position), noBreakChar, word.toString().slice(position)].join('');
+ questionText += `${word} `;
+ });
+
+ questionText = questionText.trimEnd();
+ } else {
+ questionText = this.questionText.toString();
}
- return labelText;
+ return questionText;
}
}
},
diff --git a/src/common-components/textbox-question/textbox-question.spec.js1 b/src/common-components/textbox-question/textbox-question.spec.js1
index 721fba9d5..63214c126 100644
--- a/src/common-components/textbox-question/textbox-question.spec.js1
+++ b/src/common-components/textbox-question/textbox-question.spec.js1
@@ -1,12 +1,17 @@
import { shallowMount } from "@vue/test-utils";
import textboxQuestion from "./textbox-question";
-import { nextTick } from "vue";
+import { maska } from 'maska';
describe("textboxQuestion.vue", () => {
it("Should return aria-disabled state", async () => {
// Act
const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
propsData: {
isDisabled: true,
},
@@ -17,11 +22,17 @@ describe("textboxQuestion.vue", () => {
// Expect
expect(input.attributes()["aria-disabled"]).toEqual("true");
+
});
it("Should render a text input", async () => {
// Act
const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
propsData: {
name: "test",
label: "unit test label",
@@ -32,11 +43,17 @@ describe("textboxQuestion.vue", () => {
const input = wrapper.find("input");
expect(input.exists()).toBe(true);
+
});
it("Should return input id", async () => {
// Act
const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
propsData: {
inputId: "input ID",
},
@@ -47,35 +64,115 @@ describe("textboxQuestion.vue", () => {
// Expect
expect(input.attributes().id).toEqual("input ID");
+
});
- it("Should return label text", async () => {
+ it("Should render the 'questionText' data value as the aria-label attribute.", async () => {
+ // Arrange
+ //Mock CMS content
+ const questionText = "Question Text";
+ const cmsContent = {
+ QuestionText: questionText,
+ };
+
// Act
const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
+ });
+ await wrapper.setData({
+ questionText: questionText,
+ });
+ wrapper.vm.initializeComponent(cmsContent);
+
+ // Assert
+ expect(wrapper.find("label").attributes('aria-label')).toBe(questionText);
+ wrapper.unmount();
+
+ });
+
+ it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {
+ // Arrange
+ //Mock CMS content
+ const questionText = "Question Text";
+ const cmsContent = {
+ QuestionText: questionText,
+ };
+
+ // Act
+ const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
+ });
+ await wrapper.setData({
+ questionText: questionText,
+ });
+ wrapper.vm.initializeComponent(cmsContent);
+
+ // Assert
+ expect(wrapper.find("label").text()).toContain(questionText);
+ wrapper.unmount();
+
+ });
+
+ it("Should render the 'questionText' data value with '⁠' after the first character as the label text when disableAutoFill is true.", async () => {
+ // Arrange
+ //Mock CMS content
+ const originalQuestionText = "Question Text";
+ // Trust me, the below instance of the string "Question Text" actually has the ⁠ in it. You just can't see it
+ // Don't believe me? Copy it and paste it into Google. Then inspect the search field element in Dev Tools,
+ // you will see "Q&uestion Text"
+ const expectedQuestionText = "Question Text";
+ const cmsContent = {
+ QuestionText: originalQuestionText,
+ };
+
+ // Act
+ const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
propsData: {
- labelText: "label text",
+ disableAutoFill: true,
+ },
+ });
+ await wrapper.setData({
+ questionText: originalQuestionText,
+ });
+ wrapper.vm.initializeComponent(cmsContent);
+
+ // Assert
+ expect(wrapper.vm.$el.children[0].innerHTML).toBe(expectedQuestionText);
+ wrapper.unmount();
+
+ });
+
+ it("Should emit new value when modelValue is changed", async () => {
+ // Act
+ const wrapper = shallowMount(textboxQuestion, {
+ global: {
+ directives: {
+ maska: maska,
+ }
+ },
+ propsData: {
+ modelValue: "val",
},
});
- // Assert
- const label = wrapper.find("label");
-
- expect(label.text()).toEqual("label text");
- });
-
- it("Should return input id", async () => {
- // Act
- const wrapper = shallowMount(textboxQuestion, {
- propsData: {
- inputId: "input ID",
- },
- });
+ await wrapper.find("input").setValue("val2");
// Assert
- const input = wrapper.find("input");
+ expect(wrapper.emitted()).toHaveProperty('change')
- // Expect
- expect(input.attributes().id).toEqual("input ID");
});
});
diff --git a/src/common-components/textbox-question/textbox-question.vue b/src/common-components/textbox-question/textbox-question.vue
index 5fba4c165..00dcdfb88 100644
--- a/src/common-components/textbox-question/textbox-question.vue
+++ b/src/common-components/textbox-question/textbox-question.vue
@@ -15,7 +15,7 @@
autocomplete="off"
:class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']"
:validationRules="validationRules"
- @input="handleChange"
+ @change="handleChange"
@blur="handleBlur" />
{{ errorMessage }}
@@ -91,22 +91,25 @@ export default {
},
labelText: {
get: function () {
- let labelText = this.questionText;
+ const noBreakChar = "⁠";
+ var questionText = "";
if (this.disableAutoFill) {
- var noBreakChar = "⁠";
- var position = 1;
- labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
+ var words = this.questionText.toString().split(/[ ]+/);
+ words.forEach(function (word) {
+ const position = 1;
+ word = [word.toString().slice(0, position), noBreakChar, word.toString().slice(position)].join('');
+ questionText += `${word} `;
+ });
+
+ questionText = questionText.trimEnd();
+ } else {
+ questionText = this.questionText.toString();
}
- return labelText;
+ return questionText;
}
}
},
- watch: {
- value(newValue) {
- this.handleChange(newValue);
- }
- }
};
diff --git a/src/constants/application-config.js b/src/constants/application-config.js
index a7841e5a9..b1ab342a7 100644
--- a/src/constants/application-config.js
+++ b/src/constants/application-config.js
@@ -1,6 +1,6 @@
const applicationConfig = {
- CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
+ CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
SAVED_SESSION_TIMEOUT_DAYS: 45,
diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js
index 47eb50ab8..ca1555e7b 100644
--- a/src/constants/endpoints.js
+++ b/src/constants/endpoints.js
@@ -47,6 +47,10 @@ const endpoints = {
url: "/vehicle/api/v1/vehicle/lookup-vin-by-plate",
method: "POST",
},
+ LookupVinByAddress: {
+ url: "/vehicle/api/v1/vehicle/lookup-vin-by-address",
+ method: "POST",
+ },
GetPartsOrQuestions: {
url: "/parts/api/v1/parts/parts-or-questions",
method: "POST",
diff --git a/src/constants/error-messages.js b/src/constants/error-messages.js
index c3976d0f9..17012ab68 100644
--- a/src/constants/error-messages.js
+++ b/src/constants/error-messages.js
@@ -11,11 +11,14 @@ const errorMessages = {
CITY_REQUIRED: "Please enter your city",
STATE_REQUIRED: "Please enter your state",
ZIP_REQUIRED: "Please enter your ZIP",
+ ZIP_FORMAT: "Please enter a valid ZIP",
LICENSE_PLATE_REQUIRED: "Please enter your license plate number",
FIRST_NAME_REQUIRED: "Please enter your first name",
LAST_NAME_REQUIRED: "Please enter your last name",
EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
EMAIL_ADDRESS_FORMAT: "Please enter a valid email address",
+ SERVICE_ZIP_REQUIRED: "Please enter your Service ZIP",
+ SERVICE_ZIP_FORMAT: "Please enter a valid Service ZIP",
VIN_REQUIRED: "Please enter your VIN",
VIN_FORMAT: "Please enter a valid VIN",
};
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index 8de78f70c..74dac5043 100644
--- a/src/constants/store-actions.js
+++ b/src/constants/store-actions.js
@@ -12,6 +12,7 @@ const storeActions = {
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
+ LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
SAVE_ORDER: "saveOrder",
LOAD_ORDER: "loadOrder",
diff --git a/src/global-methods.js b/src/global-methods.js
index e7996c4fa..5e4d04ea3 100644
--- a/src/global-methods.js
+++ b/src/global-methods.js
@@ -7,10 +7,10 @@ import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
export default {
callHttpClient({ method, endpoint, payload, logApiCall = true }) {
return new Promise((resolve, reject) => {
- const apiGatewayUrl = applicationConfig.CONSUMER_APIGATEWAY_URL;
+ const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "FixMyGlass" });
- axios({ method: method, url: apiGatewayUrl + endpoint, data: payloadAndAnalyticsData, crossDomain: true, responseType: {} })
+ axios({ method: method, url: cfDistroUrl + endpoint, data: payloadAndAnalyticsData, crossDomain: true, responseType: {} })
.then((response) => {
if (logApiCall) {
diff --git a/src/helpers/damage-helper.js b/src/helpers/damage-helper.js
index 453aadf69..e56761dc5 100644
--- a/src/helpers/damage-helper.js
+++ b/src/helpers/damage-helper.js
@@ -7,7 +7,7 @@ export function getDamageString() {
}
export async function isGlassAvailableForCarId(carId){
- const newGlassOptions = await baseMixin.methods.dispatchNonBlockingStoreAction(
+ const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS,
{ carId: carId }
);
@@ -24,8 +24,8 @@ export async function isGlassAvailableForCarId(carId){
for(const option of currentGlassOptions){
if(!newGlassOptions.data[optionsMap[option.location]].availableReplacementOptions.includes(option.name)){
return false;
- }
+ }
}
return true;
- }
\ No newline at end of file
+ }
diff --git a/src/helpers/damage-helper.spec.js b/src/helpers/damage-helper.spec.js
index abc8defe2..ad5f8f878 100644
--- a/src/helpers/damage-helper.spec.js
+++ b/src/helpers/damage-helper.spec.js
@@ -13,30 +13,4 @@ jest.mock("@/store", () => ({
const damage = getDamageString();
expect(damage).toEqual("Windshield")
});
- });
-
- // describe("damage-helper.js", () => {
- // it("Should return false if no mismatches between each array", async () => {
- // const updatedOptions = {
- // data: {
- // windshieldOptions: {availableReplacementOptions: ["windshield"]}
- // }
- // }
- // baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn().mockImplementation(()=> {
- // return updatedOptions;
- // });
- // const misMatch = await isGlassAvailableForCarId();
- // expect(misMatch).toEqual(false);
- // });
- // });
-
- // describe("damage-helper.js", () => {
- // it("Should return true if there are any mismatches between arrays", () => {
- // const newOptions = {
- // windshieldOptions: {availableReplacementOptions: ["window"]}
- // }
- // const currentOptions = [{location: "Windshield", name: "windshield"}];
- // const misMatch = compareGlassOptions(newOptions, currentOptions);
- // expect(misMatch).toEqual(true);
- // });
- // });
\ No newline at end of file
+ });
\ No newline at end of file
diff --git a/src/helpers/heritage-integration/navigation-helper.js b/src/helpers/heritage-integration/navigation-helper.js
index 989dec817..49c4ab354 100644
--- a/src/helpers/heritage-integration/navigation-helper.js
+++ b/src/helpers/heritage-integration/navigation-helper.js
@@ -95,7 +95,7 @@ async function getLatestPageForRedirection() {
if (store.getters.vehicle.vin) {
return fmgPageValues.LICENSE_PLATE_LOOKUP;
} else {
- return fmgPageValues.ESTIMATE;
+ return fmgPageValues.VIN_LOOKUP;
}
}
}
@@ -134,7 +134,7 @@ function overrideYmmsDirectionIfNeeded(toRoute) {
/* istanbul ignore next */
function isVinRelatedPage(toRoute) {
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
-
+
return fmgPageValue === fmgPageValues.VIN_LOOKUP ||
fmgPageValue === fmgPageValues.LICENSE_PLATE_LOOKUP ||
fmgPageValue === fmgPageValues.ADDRESS_LOOKUP ||
diff --git a/src/helpers/heritage-integration/navigation-helper.spec.js b/src/helpers/heritage-integration/navigation-helper.spec.js
index 17561ba52..de5df0239 100644
--- a/src/helpers/heritage-integration/navigation-helper.spec.js
+++ b/src/helpers/heritage-integration/navigation-helper.spec.js
@@ -283,7 +283,7 @@ describe("getPageToRouteExistingOrderTo", () => {
const result = await getPageToRouteExistingOrderTo(toRoute, false);
//Assert
- expect(result).toBe('estimate');
+ expect(result).toBe('vin-lookup');
});
test("getPageToRouteExistingOrderTo, existing order, should return heritage", async () => {
diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js
index 35c3b8570..b18b64a85 100644
--- a/src/helpers/heritage-integration/order-helper.js
+++ b/src/helpers/heritage-integration/order-helper.js
@@ -18,7 +18,7 @@ export async function loadOrderIfPresent() {
// Reset state if cookie says to.
if (funnelCookie.ShouldResetState) {
- baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE);
+ baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
deleteFunnelCookie();
return null;
}
@@ -34,10 +34,10 @@ export async function loadOrderIfPresent() {
update the cookie.
*/
export async function saveOrder() {
- const savedOrderInfo = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SAVE_ORDER);
+ const savedOrderInfo = await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_ORDER);
// Save the referral information back from the store.
- await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SET_REFERRAL_INFORMATION, {
+ await baseMixin.methods.dispatchStoreAction(storeActions.SET_REFERRAL_INFORMATION, {
referralNumber: savedOrderInfo.data.referralNumber,
referralCorrelationId: savedOrderInfo.data.referralCorrelationId,
referralDate: savedOrderInfo.data.referralDate,
@@ -56,7 +56,7 @@ export async function saveOrder() {
and returns the response.
*/
async function loadOrder(referralNumber, referralDate, referralCorrelationId, accountNumber) {
- const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
+ const response = await baseMixin.methods.dispatchStoreAction(storeActions.LOAD_ORDER,
{
referralNumber: referralNumber.toString(),
referralDate: referralDate,
diff --git a/src/helpers/heritage-integration/order-helper.spec.js b/src/helpers/heritage-integration/order-helper.spec.js
index cbe54ba25..803c94b6b 100644
--- a/src/helpers/heritage-integration/order-helper.spec.js
+++ b/src/helpers/heritage-integration/order-helper.spec.js
@@ -48,7 +48,7 @@ describe("loadOrderIfPresent", () => {
// Assert
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.RESET_STATE);
+ expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(storeActions.RESET_STATE);
});
test("Funnel cookie is null => store is unchanged", () => {
@@ -68,7 +68,7 @@ describe("loadOrderIfPresent", () => {
// Assert
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE);
+ expect(mocks.baseMixin.methods.dispatchStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE);
});
test("Funnel cookie valid, should call loadOrder", async () => {
@@ -90,7 +90,7 @@ describe("loadOrderIfPresent", () => {
// Assert
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.LOAD_ORDER);
+ expect(mocks.baseMixin.methods.dispatchStoreAction).not.toHaveBeenCalledWith(storeActions.LOAD_ORDER);
expect(result.ReferralNumber).toBe(123456);
expect(result.vehicle.year).toBe(2010);
});
@@ -127,8 +127,8 @@ describe("saveOrder", () => {
await saveOrder();
// Assert
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SAVE_ORDER);
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SET_REFERRAL_INFORMATION, {
+ expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(storeActions.SAVE_ORDER);
+ expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(storeActions.SET_REFERRAL_INFORMATION, {
referralNumber: mockReferralNumber,
referralDate: mockReferralDate,
referralCorrelationId: mockCorrelationId
diff --git a/src/helpers/unit-test-helper.js b/src/helpers/unit-test-helper.js
index f7e8047ca..770dd893e 100644
--- a/src/helpers/unit-test-helper.js
+++ b/src/helpers/unit-test-helper.js
@@ -9,22 +9,23 @@ import baseMixin from "@/mixins/base-mixin";
import { getCookieDomainValue } from "@/helpers/heritage-integration/cookie-helper";
import { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents } from "@/constants/analytics";
import { queryStrings } from "@/constants/query-strings";
+import { routerParams } from "@/router/router-constants/router-params";
// Common methods
export function getMountOptions(mockData) {
// Define our mocks to attached to the 'global' object for Vue/Jest.
const mocks = {};
- //this is mocking if you use the mixin directly(baseMixin.methods.dispatchNonBlockingStoreAction) vs this.dispatchNonBlockingStoreAction
- setupBaseMixinDispatchNonBlockingStoreAction(mockData);
+ //this is mocking if you use the mixin directly(baseMixin.methods.dispatchStoreAction) vs this.dispatchStoreAction
+ setupBaseMixinDispatchStoreAction(mockData);
mocks.pushEventToGA = jest.fn();
mocks.pushPageViewToGA = jest.fn();
mocks.logEvent = jest.fn();
mocks.pushExperimentsToDataLayer = jest.fn();
- mocks.dispatchNonBlockingStoreAction = jest.fn();
- mocks.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
+ mocks.dispatchStoreAction = jest.fn();
+ mocks.dispatchStoreAction.mockImplementation((actionName) => {
let actionFilterResult = mockData.actionList.filter(
(x) => x.actionName == actionName
);
@@ -48,6 +49,7 @@ export function getMountOptions(mockData) {
mocks.GaLabels = GaLabels;
mocks.GaEvents = GaEvents;
mocks.queryStrings = queryStrings;
+ mocks.routerParams = routerParams;
// Mock $store and $router when accessing this.$store/$router
mocks.$store = mockData.store;
@@ -63,7 +65,7 @@ export function getMountOptions(mockData) {
}
export function setupMocksForJsFiles(mockData = {}) {
- setupBaseMixinDispatchNonBlockingStoreAction(mockData);
+ setupBaseMixinDispatchStoreAction(mockData);
return { baseMixin };
}
@@ -103,10 +105,10 @@ export function setupCookies({ funnelCookieValue = "", includeHeritageCookie = t
}
// Private methods
-function setupBaseMixinDispatchNonBlockingStoreAction(mockData) {
+function setupBaseMixinDispatchStoreAction(mockData) {
if (mockData.actionList !== undefined) {
- baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
- baseMixin.methods.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
+ baseMixin.methods.dispatchStoreAction = jest.fn();
+ baseMixin.methods.dispatchStoreAction.mockImplementation((actionName) => {
let actionFilterResult = mockData.actionList.filter(
(x) => x.actionName == actionName
);
diff --git a/src/helpers/validation-rules.spec.js b/src/helpers/validation-rules.spec.js
index 323e05ff1..7e0c6304e 100644
--- a/src/helpers/validation-rules.spec.js
+++ b/src/helpers/validation-rules.spec.js
@@ -1,4 +1,5 @@
import { required } from "@/helpers/validation-rules";
+import { regex } from "@/helpers/validation-rules";
describe("validation-rules.vue", () => {
test("required rules should return error if value missing", () => {
@@ -15,15 +16,57 @@ describe("validation-rules.vue", () => {
});
describe("validation-rules.vue", () => {
- test("required rules should return true if value present", () => {
-
- //Arrange
- const testFn = required("an error");
-
- //Act
- const testResponse = testFn('some value');
-
- //Assert
- expect(testResponse).toBe(true);
- });
- });
\ No newline at end of file
+ test("required rules should return true if value present", () => {
+
+ //Arrange
+ const testFn = required("an error");
+
+ //Act
+ const testResponse = testFn('some value');
+
+ //Assert
+ expect(testResponse).toBe(true);
+ });
+});
+
+describe("validation-rules.vue", () => {
+ test("regex rules should return true if value is not present", () => {
+
+ //Arrange
+ const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex
+
+ //Act
+ const testResponse = testFn();
+
+ //Assert
+ expect(testResponse).toBe(true);
+ });
+});
+
+describe("validation-rules.vue", () => {
+ test("regex rules should return false if value is present but does not match regular expression", () => {
+
+ //Arrange
+ const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex
+
+ //Act
+ const testResponse = testFn('4321'); // needs to be 5 numbers
+
+ //Assert
+ expect(testResponse).toBe("an error");
+ });
+});
+
+describe("validation-rules.vue", () => {
+ test("regex rules should return true if value is present and does match regular expression", () => {
+
+ //Arrange
+ const testFn = regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, "an error"); // Using Zip regex
+
+ //Act
+ const testResponse = testFn('43213'); // needs to be 5 numbers
+
+ //Assert
+ expect(testResponse).toBe(true);
+ });
+});
\ No newline at end of file
diff --git a/src/layouts/address-lookup/address-lookup.spec.js1 b/src/layouts/address-lookup/address-lookup.spec.js1
index e69de29bb..4443100c3 100644
--- a/src/layouts/address-lookup/address-lookup.spec.js1
+++ b/src/layouts/address-lookup/address-lookup.spec.js1
@@ -0,0 +1,244 @@
+// Components
+import addressLookup from "@/layouts/address-lookup/address-lookup.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 customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
+import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions";
+
+// Supporting Files
+import { settleAllPromises } from "@/helpers/layout-helper.js";
+import baseMixin from "@/mixins/base-mixin";
+import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
+import { mount, flushPromises } from "@vue/test-utils";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import { maska } from 'maska';
+import { nextTick } from "vue";
+import { storeActions } from "@/constants/store-actions";
+import { storeMutations } from "@/constants/store-mutations";
+import store from "@/store";
+import { validate } from "vee-validate";
+
+// Mock our module for promises.
+jest.mock("@/helpers/layout-helper.js", () => ({
+ settleAllPromises: jest.fn(),
+}));
+
+// Mock fetchCmsContentForPage
+jest.mock("@/helpers/cms-content-helper", () => ({
+ fetchCmsContentForPage: jest.fn(),
+}));
+
+describe("address-lookup.vue", () => {
+ test("Page header is initialized with api data", async (done) => {
+ //Arrange
+ const pageHeaderWidgetHeaderText = "Select Damage";
+ const { wrapper, apiPromise } = setupMocks({
+ pageHeaderWidgetHeaderText: pageHeaderWidgetHeaderText,
+ });
+
+ //Act
+ addressLookup.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { fmgPage: "address-lookup" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ //Assert
+ apiPromise.finally(() => {
+ expect(funnelSubHeader.methods.initializeComponent).toHaveBeenCalledWith(
+ pageHeaderWidgetHeaderText
+ );
+ done();
+ });
+ });
+
+ test("Customer Questions component is initialized with api data", async (done) => {
+ //Arrange
+ const StreetAddressQuestionWidget = { QuestionText: "test" };
+ const CityQuestionWidget = { QuestionText: "test" };
+ const StateQuestionWidget = { QuestionText: "test" };
+ const ZipQuestionWidget = { QuestionText: "test" };
+ const FirstNameQuestionWidget = { QuestionText: "test" };
+ const LastNameQuestionWidget = { QuestionText: "test" };
+ const EmailAddressQuestionWidget = { QuestionText: "test" };
+ const AlertVerificationWarningWidget = { HeaderText: "test", BodyText: "test" };
+ const AlertNoMatchWarningWidget = { HeaderText: "test", BodyText: "test" };
+
+ const widgets = [
+ StreetAddressQuestionWidget,
+ CityQuestionWidget,
+ StateQuestionWidget,
+ ZipQuestionWidget,
+ AlertVerificationWarningWidget,
+ AlertNoMatchWarningWidget,
+ FirstNameQuestionWidget,
+ LastNameQuestionWidget,
+ EmailAddressQuestionWidget,
+ ];
+
+ const { wrapper, apiPromise } = setupMocks({
+ cmsContent: widgets,
+ });
+
+ //Act
+ addressLookup.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { fmgPage: "address-lookup" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ //Assert
+ apiPromise.finally(() => {
+ expect(customerQuestions.methods.initializeComponent).toHaveBeenCalledWith(
+ widgets
+ );
+ done();
+ });
+ });
+
+ });
+
+
+
+ function setupMocks({
+ pageHeaderWidgetHeaderText = {},
+ mountOptionsMockData = {
+ router: {
+ navigate: jest.fn(),
+ },
+ store: {
+ getters: {
+ vehicle: {},
+ },
+ },
+ },
+ }) {
+ //Mock api responses
+ baseMixin.methods.dispatchStoreAction = jest.fn();
+ const apiResponses = {
+ cmsContent: {
+ FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
+ VehicleBannerWidget: {
+ GenericVehicleImage:
+ "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
+ },
+ FunnelHeaderWidget: {
+ LogoImage:
+ "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
+ },
+ StreetAddressQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ CityQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ StateQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ ZipQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ FirstNameQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ LastNameQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ EmailAddressQuestionWidget: {
+ QuestionText:
+ "test"
+ },
+ AlertVerificationWarningWidget: {
+ HeaderText:
+ "test",
+ BodyText:
+ "test",
+ },
+ AlertNoMatchWarningWidget: {
+ HeaderText:
+ "test",
+ BodyText:
+ "test",
+ },
+
+ },
+ };
+
+ const apiPromise = Promise.resolve(apiResponses);
+
+ settleAllPromises.mockImplementation(() => apiPromise);
+ 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(),
+ };
+
+ customerQuestions.methods = {
+ initializeComponent: jest.fn(),
+ };
+
+ addressQuestions.methods = {
+ initializeComponent: jest.fn(),
+ setupAddressLookup: jest.fn(),
+ };
+
+ const mountOptions = getMountOptions(mountOptionsMockData);
+ mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
+
+ mountOptions.global.directives = {
+ maska: maska
+ };
+
+ const wrapper = mount(addressLookup, 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 customerQuestionsWrapper = wrapper.findComponent({ name: "customerQuestions" });
+ customerQuestionsWrapper.vm.initializeComponent =
+ customerQuestions.methods.initializeComponent;
+
+ const addressQuestionsWrapper = wrapper.findComponent({ name: "addressQuestions" });
+ addressQuestionsWrapper.vm.initializeComponent =
+ addressQuestions.methods.initializeComponent;
+ addressQuestionsWrapper.vm.setupAddressLookup =
+ addressQuestions.methods.setupAddressLookup;
+
+ return { wrapper, apiPromise };
+ }
\ No newline at end of file
diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue
index 7a90ee123..895148575 100644
--- a/src/layouts/address-lookup/address-lookup.vue
+++ b/src/layouts/address-lookup/address-lookup.vue
@@ -5,15 +5,54 @@
ref="theForm"
v-slot="{ meta }"
autocomplete="off" >
-
-
+
@@ -26,14 +65,27 @@ 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 customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
+import alert from "@/ux-components/alert/alert";
+import textboxQuestion from "@/common-components/textbox-question/textbox-question";
+
import { Form } from "vee-validate";
+import { defineRule } from "vee-validate";
+import { required } from "@/helpers/validation-rules";
+import { regex } from "@/helpers/validation-rules";
+import { errorMessages } from "@/constants/error-messages";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
-import { storeActions } from "@/constants/store-actions";
import store from "@/store";
+import { storeActions } from "@/constants/store-actions";
+import { storeMutations } from "@/constants/store-mutations";
+import baseMixin from "@/mixins/base-mixin";
+import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
+import { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
+defineRule("service-zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED));
+defineRule("service-zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
export default {
name: "address-lookup",
@@ -53,46 +105,29 @@ export default {
// Call the "next" function to complete the transition to this page.
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
- );
- vm.$refs.customerQuestions.initializeComponent([
- resultMap.cmsContent.StreetAddressQuestionWidget,
- resultMap.cmsContent.CityQuestionWidget,
- resultMap.cmsContent.StateQuestionWidget,
- resultMap.cmsContent.ZipQuestionWidget,
- resultMap.cmsContent.AlertVerificationWarningWidget,
- resultMap.cmsContent.AlertNoMatchWarningWidget,
- resultMap.cmsContent.FirstNameQuestionWidget,
- resultMap.cmsContent.LastNameQuestionWidget,
- resultMap.cmsContent.EmailAddressQuestionWidget,
-
- ]
- );
+ vm.setCmsContent(resultMap.cmsContent);
});
},
data() {
return {
customerQuestions: {
addressQuestions: {
- streetAddress: "",
- city: "",
- state: "",
- zip: "",
+ streetAddress: this.getRegistrationAddressFromStore(),
+ city: this.getRegistrationCityFromStore(),
+ state: this.getRegistrationStateFromStore(),
+ zip: this.getRegistrationZipFromStore(),
},
- firstName: "",
- lastName: "",
- emailAddress: "",
- }
+ firstName: this.getRegistrationFirstNameFromStore(),
+ lastName: this.getRegistrationLastNameFromStore(),
+ emailAddress: this.getEmailFromStore(),
+ },
+ serviceZip: this.getServiceZipFromStore(),
+ displayNonServiceableZipAlert: false,
+ displayVinNotFoundAlert: false,
+ displayMatchedDifferentVehicleAlert: false,
+ displayVinLookupByHomeAddressNotAllowedAlert: false,
+ previousCarIdFound: "",
+ customAlertData: {},
}
},
methods: {
@@ -100,9 +135,220 @@ export default {
return store.getters.vehicle.carId !== null;
},
resetDependentState() {
- // Invokes
+ store.commit(storeMutations.UPDATE_REGISTRATION_LICENSE_PLATE, null);
+ store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
+ },
+ backButtonAction() {
+ // route to move backwards
+ this.$router.navigate(
+ this.navigationScenarios.CLICKED_BACK,
+ this.$route
+ );
+ },
+ getRegistrationAddressFromStore() {
+ return store.getters.vehicle.registration.address;
+ },
+ getRegistrationCityFromStore() {
+ return store.getters.vehicle.registration.city;
+ },
+ getRegistrationStateFromStore() {
+ return store.getters.vehicle.registration.state;
+ },
+ getRegistrationZipFromStore() {
+ return store.getters.vehicle.registration.zipCode;
+ },
+ getRegistrationFirstNameFromStore() {
+ return store.getters.vehicle.registration.firstName;
+ },
+ getRegistrationLastNameFromStore() {
+ return store.getters.vehicle.registration.lastName;
+ },
+ getEmailFromStore() {
+ return store.getters.order.customer.emailAddress;
+ },
+ getServiceZipFromStore() {
+ return store.getters.order.serviceLocation.zip;
+ },
+ async forwardButtonAction() {
+ this.resetWarningsAndErrors();
+
+ // Lookup VIN(s) with the provided address
+ const vinLookup = await this.lookupVin(
+ this.customerQuestions.lastName,
+ this.customerQuestions.addressQuestions.streetAddress,
+ this.customerQuestions.addressQuestions.zip,
+ this.customerQuestions.addressQuestions.state
+ );
+
+ if (!vinLookup.data.isStatePermissible) {
+ // State Restrictions forbid lookup by address
+ this.displayVinLookupByHomeAddressNotAllowedAlert = true;
+ this.$refs.funnelFooter.removeLoader();
+ return;
+ }
+
+ // Validate if the original or service zip provided is serviceable
+ const zipValidation = this.serviceZip ? await this.validateZip(this.serviceZip) : await this.validateZip(this.customerQuestions.addressQuestions.zip);
+ if (!zipValidation.data.isServiceable) {
+ this.displayNonServiceableZipAlert = true;
+ this.showServiceZipField = true;
+ this.$refs.funnelFooter.removeLoader();
+ }
+
+ const carEntered = store.getters.vehicle;
+ const carsFound = vinLookup.data.vinVehicles;
+ if (carsFound.length == 0) {
+ // No VINs found
+ this.displayVinNotFoundAlert = true;
+ this.$refs.funnelFooter.removeLoader();
+ return;
+ } else if (carsFound.length == 1) {
+ var carFound = carsFound[0].vehicle;
+
+ if (carEntered.carId == carFound.carId || carFound.carId == this.previousCarIdFound) {
+ // update data
+ this.updateVehicleInfo(carFound.vin, carFound);
+ this.updateCustomerInfo();
+
+ // navigate forward
+ this.navigateForward(carEntered, carsFound);
+ } else {
+ // Display Alert
+ this.customAlertData.vehicleInfo = carFound;
+ this.displayMatchedDifferentVehicleAlert = true;
+
+ // Update button "Continue with..."
+ this.$refs.funnelFooter.updateButtonText(`Continue with ${carFound.year} ${carFound.make} ${carFound.model}`);
+ this.$refs.funnelFooter.removeLoader();
+ }
+
+ this.previousCarIdFound = carFound.carId;
+
+ } else if (vinLookup.data.vinVehicles.length > 1) {
+ this.updateCustomerInfo();
+
+ // navigate forward
+ this.navigateForward(carEntered, carsFound);
+ }
+
},
+ resetWarningsAndErrors() {
+ this.displayVinNotFoundAlert = false;
+ this.displayNonServiceableZipAlert = false;
+ this.displayMatchedDifferentVehicleAlert = false;
+ this.displayVinLookupByHomeAddressNotAllowedAlert = false;
+ },
+ async navigateForward(carEntered, carsFound) {
+ if (carsFound.length == 1) {
+ // get the damage options for the car that was found
+ const carFound = carsFound[0].vehicle;
+ const glassOptions = await baseMixin.methods.dispatchStoreAction(
+ storeActions.GET_DAMAGE_OPTIONS,
+ { carId: carFound.carId }
+ );
+
+ // if the car entered is the same as the car found OR the glass options for the found car match the users damage selections
+ if (carEntered.carId == carFound.carId || isGlassAvailableForCarId(carFound.carId)) {
+ navigateAfterSaveToHeritageFunnel(this.$route);
+ } else {
+ // if not then navigate to the "vehicle-damage" page
+ this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {});
+ }
+ } else if (carsFound.length > 1) {
+ // if multiple cars were found
+ if (carsFound.find(car => car.carId === carEntered.carId)) {
+ // and one of them matches the car id entered
+ navigateAfterSaveToHeritageFunnel(this.$route);
+ } else {
+ // and there is no match, navigate to "address-vehicle" page
+ this.$router.navigateAfterSave(this.navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, this.$route, {}, {}, carsFound);
+ }
+ }
+
+ },
+ validateZip(zip) {
+ return baseMixin.methods.dispatchStoreAction(
+ storeActions.VALIDATE_ZIP,
+ { zip });
+ },
+ lookupVin(lastName, streetAddress, zip, state) {
+ return baseMixin.methods.dispatchStoreAction(
+ storeActions.LOOKUP_VIN_BY_ADDRESS,
+ {
+ licenseLastName: lastName,
+ licenseStreetAddress: streetAddress,
+ licenseZip: zip,
+ licenseState: state
+ }, false
+ );
+ },
+ updateVehicleInfo(vin, vehicleInfo) {
+ store.commit(storeMutations.UPDATE_VEHICLE_VIN, vin);
+ store.commit(storeMutations.UPDATE_YEAR, vehicleInfo.year);
+ store.commit(storeMutations.UPDATE_MAKE, vehicleInfo.make);
+ store.commit(storeMutations.UPDATE_MODEL, vehicleInfo.model);
+ store.commit(storeMutations.UPDATE_STYLE, vehicleInfo.style);
+ store.commit(storeMutations.UPDATE_CAR_ID, vehicleInfo.carId);
+ store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, vehicleInfo.category);
+ store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, vehicleInfo.imageUrl);
+ store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, vehicleInfo.imageVifNumber);
+ store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, vehicleInfo.imageColor);
+ },
+ updateCustomerInfo() {
+ store.commit(storeMutations.UPDATE_REGISTRATION_ADDRESS, this.customerQuestions.addressQuestions.streetAddress);
+ store.commit(storeMutations.UPDATE_REGISTRATION_CITY, this.customerQuestions.addressQuestions.city);
+ store.commit(storeMutations.UPDATE_REGISTRATION_STATE, this.customerQuestions.addressQuestions.state);
+ store.commit(storeMutations.UPDATE_REGISTRATION_ZIP_CODE, this.customerQuestions.addressQuestions.zipCode);
+ store.commit(storeMutations.UPDATE_REGISTRATION_FIRST_NAME, this.customerQuestions.firstName);
+ store.commit(storeMutations.UPDATE_REGISTRATION_LAST_NAME, this.customerQuestions.lastName);
+ store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, this.serviceZip);
+ store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.customerQuestions.email);
+ },
+
+ },
+ computed: {
+ AlertNonServiceableZipHeader(){
+ let zip = this.serviceZip ? this.serviceZip : this.customerQuestions.addressQuestions.zip;
+ let text = this.getCmsContent("AlertNonServiceableZipWidget", "HeadlineText").replaceAll("{custom:serviceZip}", zip);
+ return text;
+ },
+ AlertNonServiceableZipBody(){
+ return this.getCmsContent("AlertNonServiceableZipWidget", "BodyText");
+ },
+ AlertMatchedDifferentVehicleHeader(){
+ let text = this.getCmsContent("AlertMatchedDifferentVehicleWidget", "HeadlineText").replaceAll("{custom:glassText}", getDamageString());
+ return text;
+ },
+ AlertMatchedDifferentVehicleBody(){
+ let content = this.getCmsContent("AlertMatchedDifferentVehicleWidget", "BodyText");
+ content = content.replaceAll("{custom:glassText}", getDamageString());
+ let vinYmmFound = `${this.customAlertData?.vehicleInfo?.year} ${this.customAlertData?.vehicleInfo?.make} ${this.customAlertData?.vehicleInfo?.model}`;
+ let vinYmmExpected = `${store.getters.vehicle.year} ${store.getters.vehicle.make} ${store.getters.vehicle.model}`;
+
+ content = content.replaceAll("{custom:vinYmmFound}", vinYmmFound);
+ content = content.replaceAll("{custom:vinYmmExpected}", vinYmmExpected);
+
+ return content;
+ },
+ },
+ watch: {
+ customerQuestions: {
+ handler(newValue) {
+ // if they modify one of the lookup fields (address, city, state, zip, or lastName), then modify the button text back to “Get my personalized quote”
+ this.$refs.funnelFooter.updateButtonText(this.getCmsContent("FunnelFooterWidget", "ForwardButtonText"));
+ this.showServiceZipField = false;
+ this.resetWarningsAndErrors();
+ },
+ deep: true
+ },
+ serviceZip: {
+ handler(newValue) {
+ // if they modify the service zip, then hide the error message”
+ this.displayNonServiceableZipAlert = false;
+ },
+ }
+
},
components: {
funnelHeader,
@@ -110,6 +356,8 @@ export default {
vehicleBanner,
funnelSubHeader,
customerQuestions,
+ textboxQuestion,
+ alert,
Form
},
};
diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
index 0954caf34..4a772e468 100644
--- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
+++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
@@ -1,36 +1,34 @@
-
-
-
-
-
+
+
+
+
@@ -41,17 +39,17 @@ import textboxQuestion from "@/common-components/textbox-question/textbox-questi
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
import alert from "@/ux-components/alert/alert";
import { applicationConfig } from "@/constants/application-config.js";
-import { computed } from 'vue';
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
+import { regex } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
-//import store from "@/store";
// DEFINE VALIDATION RULES
defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQUIRED));
defineRule("city-required", required(errorMessages.CITY_REQUIRED));
defineRule("state-required", required(errorMessages.STATE_REQUIRED));
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
+defineRule("zip-format", regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.ZIP_FORMAT));
export default ({
name: "address-questions",
@@ -67,19 +65,7 @@ export default ({
}),
},
validationRules: String,
- },
- setup(props, { emit }) {
- // Please do not modify, this "computed" is used to track and report
- // this object's property changes to the parent component
- const addressModel = computed({ // Use computed to wrap the object
- get: () => props.modelValue,
- set: (value) => emit('update:modelValue', value),
- });
-
- return {
- addressModel,
- };
- },
+ },
data() {
return {
showAddressFields: false,
@@ -148,128 +134,130 @@ export default ({
'WY': 'Wyoming',
}
}
- }
+ },
+ addressModel: {
+ get: function() {
+ return this.modelValue;
+ },
+ set: function(newValue) {
+ this.$emit("update:modelValue", newValue);
+ }
+ },
},
methods: {
- initializeComponent(cmsContent) {
- this.$refs.autocomplete.initializeComponent(cmsContent[0].QuestionText);
- this.$refs.city.initializeComponent(cmsContent[1].QuestionText);
- this.$refs.state.initializeComponent(cmsContent[2].QuestionText);
- this.$refs.zip.initializeComponent(cmsContent[3].QuestionText);
+ setupAddressLookup() {
+ const addressField1 = document.getElementById("autocomplete");
+ const self = this;
- // assign alert texts to this component
- this.alertHeadlineVerificationWarning = cmsContent[4].HeadlineText;
- this.alertCopyVerificationWarning = cmsContent[4].BodyText;
+ const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
- this.alertHeadlineNoMatchWarning = cmsContent[5].HeadlineText;
- this.alertCopyNoMatchWarning = cmsContent[5].BodyText;
+ this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`)
+ .then(() => {
+ // Script is loaded, initialize the autocomplete textbox
+ const autocomplete = new window.google.maps.places.Autocomplete(
+ addressField1,
+ {
+ componentRestrictions: { country: ["us"] },
+ fields: ["address_components"],
+ types: ["geocode"],
+ }
+ );
+
+ // Standard place_changed event handling
+ autocomplete.addListener('place_changed', fillInAddress);
+
+ addressField1.onblur = function() {
+ const hover = document.querySelector(".pac-container .pac-item:hover");
+ // if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place
+ if (hover === null) {
+ const item = document.querySelector(".pac-container .pac-item");
+ if (item != null) {
+ const firstResult = item.textContent;
+ const geocoder = new window.google.maps.Geocoder();
+ geocoder.geocode({
+ address: firstResult
+ }, function (results, status) {
+ if (status === window.google.maps.GeocoderStatus.OK) {
+ fillInAddress(results[0]);
+ self.displayVerificationWarning = true;
+ self.displayNoMatchWarning = false;
+ }
+ });
+ }
+ else {
+ self.addressModel.city = "";
+ self.addressModel.state = "";
+ self.addressModel.zip = "";
+ self.showAddressFields = true;
+ self.displayVerificationWarning = false;
+ self.displayNoMatchWarning = true;
+ }
+ }
+ };
+
+ function fillInAddress(place) {
+ if (!place) {
+ place = autocomplete.getPlace();
+ }
+
+ if (place && place.address_components) {
+ self.addressModel.streetAddress= "";
+ self.showAddressFields = true;
+
+ for (const component of place.address_components) {
+ const componentType = component.types[0];
+
+ switch (componentType) {
+ case "street_number": {
+ self.addressModel.streetAddress = component.long_name;
+ break;
+ }
+ case "route": {
+ self.addressModel.streetAddress += ' ' + component.short_name;
+ break;
+ }
+ case "locality": {
+ self.addressModel.city = component.long_name;
+ break;
+ }
+ case "administrative_area_level_1": {
+ self.addressModel.state = component.short_name;
+ break;
+ }
+ case "postal_code": {
+ self.addressModel.zip = component.long_name;
+ break;
+ }
+
+ }
+ }
+
+ self.displayVerificationWarning = false;
+ self.displayNoMatchWarning = false;
+ }
+ else {
+ self.displayVerificationWarning = true;
+ self.displayNoMatchWarning = false;
+ }
+ }
+ })
+ .catch(() => {
+ // Failed to fetch script
+ console.log("Unable to load Google Places API script");
+ });
}
},
mounted() {
-
- const addressField1 = document.getElementById("autocomplete");
- const self = this;
-
- const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
-
- this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`)
- .then(() => {
- // Script is loaded, initialize the autocomplete textbox
- const autocomplete = new window.google.maps.places.Autocomplete(
- addressField1,
- {
- componentRestrictions: { country: ["us"] },
- fields: ["address_components"],
- types: ["address"],
- }
- );
-
- // Standard place_changed event handling
- autocomplete.addListener('place_changed', fillInAddress);
-
- addressField1.onblur = function() {
- const hover = document.querySelector(".pac-container .pac-item:hover");
-
- // if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place
- if (hover === null) {
- const item = document.querySelector(".pac-container .pac-item");
- if (item != null) {
- const firstResult = item.textContent;
- const geocoder = new window.google.maps.Geocoder();
- geocoder.geocode({
- address: firstResult
- }, function (results, status) {
- if (status === window.google.maps.GeocoderStatus.OK) {
- fillInAddress(results[0]);
- self.displayVerificationWarning = true;
- self.displayNoMatchWarning = false;
- }
- });
- }
- else {
- self.addressModel.city = "";
- self.addressModel.state = "";
- self.addressModel.zip = "";
- self.showAddressFields = true;
- self.displayVerificationWarning = false;
- self.displayNoMatchWarning = true;
- }
-
-
- }
- };
-
- function fillInAddress(place) {
- if (!place) {
- place = autocomplete.getPlace();
- }
-
- if (place && place.address_components) {
- self.addressModel.streetAddress= "";
- self.showAddressFields = true;
-
- for (const component of place.address_components) {
- const componentType = component.types[0];
-
- switch (componentType) {
- case "street_number": {
- self.addressModel.streetAddress = component.long_name;
- break;
- }
- case "route": {
- self.addressModel.streetAddress += ' ' + component.short_name;
- break;
- }
- case "locality": {
- self.addressModel.city = component.long_name;
- break;
- }
- case "administrative_area_level_1": {
- self.addressModel.state = component.short_name;
- break;
- }
- case "postal_code": {
- self.addressModel.zip = component.long_name;
- break;
- }
-
- }
- }
-
- self.displayVerificationWarning = false;
- self.displayNoMatchWarning = false;
- }
- else {
- self.displayVerificationWarning = true;
- self.displayNoMatchWarning = false;
- }
- }
- })
- .catch(() => {
- // Failed to fetch script
- console.log("Unable to load Google Places API script");
- });
+ this.setupAddressLookup();
},
+ watch: {
+ addressModel: {
+ handler(newValue){
+ this.displayNoMatchWarning = false;
+ },
+ deep: true
+ }
+ },
components: {
textboxQuestion,
dropdownQuestion,
diff --git a/src/layouts/address-lookup/customer-questions/customer-questions.vue b/src/layouts/address-lookup/customer-questions/customer-questions.vue
index 7fa324f91..e1c505c9e 100644
--- a/src/layouts/address-lookup/customer-questions/customer-questions.vue
+++ b/src/layouts/address-lookup/customer-questions/customer-questions.vue
@@ -1,18 +1,18 @@
-
+
@@ -20,12 +20,10 @@
+
\ No newline at end of file
diff --git a/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue
index 7f8e1f08a..16d67b3ee 100644
--- a/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue
+++ b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue
@@ -31,11 +31,7 @@ export default ({
}
},
props: {
- isMultiSelect: Boolean,
modelValue: Array,
- isAvailable: Boolean,
- filterByVehicleCategory: Boolean,
- name: String,
groupName: String,
cmsWidgetName: String,
},
diff --git a/src/layouts/vehicle-damage/vehicle-damage.spec.js b/src/layouts/vehicle-damage/vehicle-damage.spec.js
index 7b28e4bb5..9a9f7e6d7 100644
--- a/src/layouts/vehicle-damage/vehicle-damage.spec.js
+++ b/src/layouts/vehicle-damage/vehicle-damage.spec.js
@@ -17,6 +17,7 @@ import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
import { validate } from "vee-validate";
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
+import { routerParams } from "@/router/router-constants/router-params";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@@ -703,6 +704,57 @@ describe("vehicle-damage.vue", () => {
});
});
+describe("vehicle-damage.vue", () => {
+ test("when displayVehicleChangeAlert router params is true, the alert: 'vehicleChangeAlert' should be visible", () => {
+ // Arrange & Act
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ route: {
+ params: {
+ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true
+ }
+ }
+ }
+ });
+ // Assert
+ expect(wrapper.findComponent({ref: 'vehicleChangeAlert'}).isVisible()).toBe(true);
+ })
+});
+
+describe("vehicle-damage.vue", () => {
+ test("when displayVehicleChangeAlert router params is false, the alert: 'vehicleChangeAlert' should not be visible", () => {
+ // Arrange & Act
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ route: {
+ params: {
+ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false
+ }
+ }
+ }
+ });
+ // Assert
+ expect(wrapper.findComponent({ref: 'vehicleChangeAlert'}).isVisible()).toBe(false);
+ })
+});
+
+describe("vehicle-damage.vue", () => {
+ test("when displayVehicleChangeAlert router params is undefined, the alert: 'vehicleChangeAlert' should not be visible", () => {
+ // Arrange & Act
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ route: {
+ params: {
+ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: undefined
+ }
+ }
+ }
+ });
+ // Assert
+ expect(wrapper.findComponent({ref: 'vehicleChangeAlert'}).isVisible()).toBe(false);
+ })
+});
+
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
//
@@ -734,32 +786,41 @@ describe("vehicle-damage.vue", () => {
});
-function setupMocks({
- pageHeaderWidgetHeaderText = {},
- mountOptionsMockData = {
+function setupMocks({pageHeaderWidgetHeaderText, mountOptionsMockData}) {
+ var pageHeaderWidgetHeaderTextDefault = {};
+ var mountOptionsMockDataDefault = {
router: {
navigate: jest.fn(),
},
+ route: {
+ params: {
+ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: false
+ }
+ },
store: {
getters: {
vehicle: {},
- payment: { insuranceCoverage: { isVerified: false } },
+ payment: {
+ insuranceCoverage: {
+ isVerified: false
+ }
+ },
},
},
- },
-}) {
+ };
+ // Combine parameters with default values
+ pageHeaderWidgetHeaderText = Object.assign(pageHeaderWidgetHeaderTextDefault, pageHeaderWidgetHeaderText);
+ mountOptionsMockData = Object.assign(mountOptionsMockDataDefault, mountOptionsMockData);
//Mock api responses
- baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
+ baseMixin.methods.dispatchStoreAction = jest.fn();
const apiResponses = {
cmsContent: {
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
VehicleBannerWidget: {
- GenericVehicleImage:
- "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
+ GenericVehicleImage: "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
},
FunnelHeaderWidget: {
- LogoImage:
- "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
+ LogoImage: "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
},
},
damageOptions: {
diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue
index 0db5c8b24..862abaced 100644
--- a/src/layouts/vehicle-damage/vehicle-damage.vue
+++ b/src/layouts/vehicle-damage/vehicle-damage.vue
@@ -9,50 +9,57 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -93,7 +100,7 @@ export default {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
const damageOptionsPromise =
- baseMixin.methods.dispatchNonBlockingStoreAction(
+ baseMixin.methods.dispatchStoreAction(
storeActions.GET_DAMAGE_OPTIONS,
{ carId: store.getters.vehicle.carId }
);
@@ -280,7 +287,7 @@ export default {
store.commit(this.storeMutations.UPDATE_GLASS_TO_REPLACE, this.selectedGlassToReplace());
- const partsData = await baseMixin.methods.dispatchNonBlockingStoreAction(this.storeActions.GET_PARTS_OR_QUESTIONS,
+ const partsData = await baseMixin.methods.dispatchStoreAction(this.storeActions.GET_PARTS_OR_QUESTIONS,
{ carId: store.getters.vehicle.carId, glassArray: this.selectedGlassToReplace()}, false);
this.navigateForward(partsData);
@@ -295,6 +302,12 @@ export default {
return;
}
+ // Temporary easter egg to navigate to address-lookup.
+ if (store.getters.vehicle.year === 2014) {
+ this.$router.navigateAfterSave(this.navigationScenarios.TEMPORARY_TO_ADDRESS_LOOKUP, this.$route, {}, {}, partsData.data);
+ return;
+ }
+
// If vin already exists, navigate directly to vin-lookup
if(this.$store.getters.vehicle.vin){
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route);
@@ -417,6 +430,9 @@ export default {
})
);
},
+ shouldDisplayVehicleChangeAlert() {
+ return this.$route.params[this.routerParams.DISPLAY_VEHICLE_CHANGE_ALERT];
+ },
shouldHideBackButton(){
return this.$store.getters.payment.insuranceCoverage.isVerified;
}
diff --git a/src/layouts/vehicle-make/make-question/make-question.vue b/src/layouts/vehicle-make/make-question/make-question.vue
index e1493c536..5bb04034d 100644
--- a/src/layouts/vehicle-make/make-question/make-question.vue
+++ b/src/layouts/vehicle-make/make-question/make-question.vue
@@ -50,7 +50,7 @@ export default {
},
methods: {
loadInitialData() {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MAKES,
{ year: store.getters.vehicle.year }
);
diff --git a/src/layouts/vehicle-model/model-question/model-question.vue b/src/layouts/vehicle-model/model-question/model-question.vue
index e3a56e8c7..d8cb21894 100644
--- a/src/layouts/vehicle-model/model-question/model-question.vue
+++ b/src/layouts/vehicle-model/model-question/model-question.vue
@@ -50,7 +50,7 @@ export default {
},
methods: {
loadInitialData() {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MODELS,
{ year: store.getters.vehicle.year, make: store.getters.vehicle.make }
);
diff --git a/src/layouts/vehicle-style/style-question/style-question.vue b/src/layouts/vehicle-style/style-question/style-question.vue
index e7a289bfd..45c0f7dea 100644
--- a/src/layouts/vehicle-style/style-question/style-question.vue
+++ b/src/layouts/vehicle-style/style-question/style-question.vue
@@ -50,7 +50,7 @@ export default {
},
methods: {
loadInitialData() {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_STYLES,
{
year: store.getters.vehicle.year,
diff --git a/src/layouts/vehicle-style/vehicle-style.spec.js b/src/layouts/vehicle-style/vehicle-style.spec.js
index 852bf248d..3d62e8f15 100644
--- a/src/layouts/vehicle-style/vehicle-style.spec.js
+++ b/src/layouts/vehicle-style/vehicle-style.spec.js
@@ -87,7 +87,7 @@ describe("vehicle-style.vue", () => {
});
describe("vehicle-style.vue", () => {
- test("selectVehicle triggers a dispatchNonBlockingStoreAction commit", async (done) => {
+ test("selectVehicle triggers a dispatchStoreAction commit", async (done) => {
//Arrange
const { wrapper, apiPromise } = setupMocks({
pageHeaderWidgetHeaderText: "Select a style to get started",
@@ -119,7 +119,7 @@ describe("vehicle-style.vue", () => {
//Assert
apiPromise.finally(() => {
- expect(wrapper.vm.dispatchNonBlockingStoreAction).toHaveBeenCalled();
+ expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
done();
});
});
diff --git a/src/layouts/vehicle-style/vehicle-style.vue b/src/layouts/vehicle-style/vehicle-style.vue
index e98cc400d..8bb542154 100644
--- a/src/layouts/vehicle-style/vehicle-style.vue
+++ b/src/layouts/vehicle-style/vehicle-style.vue
@@ -76,7 +76,7 @@ export default {
);
},
setVehicle() {
- return this.dispatchNonBlockingStoreAction(
+ return this.dispatchStoreAction(
this.storeActions.SET_VEHICLE,
{
year: this.$store.getters.vehicle.year,
diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue
index 96f62409c..fec51eafd 100644
--- a/src/layouts/vehicle-year/vehicle-year.vue
+++ b/src/layouts/vehicle-year/vehicle-year.vue
@@ -44,7 +44,7 @@ export default {
const yearQuestionInitialDataPromise = yearQuestion.methods.loadInitialData();
// Log experiment exposure
- const logExperimentExposurePromise = baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOG_EXPERIMENT_EXPOSURE,
+ const logExperimentExposurePromise = baseMixin.methods.dispatchStoreAction(storeActions.LOG_EXPERIMENT_EXPOSURE,
{
userId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
diff --git a/src/layouts/vehicle-year/year-question/year-question.vue b/src/layouts/vehicle-year/year-question/year-question.vue
index 16bac5442..a5b263fe8 100644
--- a/src/layouts/vehicle-year/year-question/year-question.vue
+++ b/src/layouts/vehicle-year/year-question/year-question.vue
@@ -48,7 +48,7 @@ export default {
},
methods: {
loadInitialData() {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_YEARS,
{}
);
diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue
index 3d79faedd..f2e31edad 100644
--- a/src/layouts/vin-lookup/vin-lookup.vue
+++ b/src/layouts/vin-lookup/vin-lookup.vue
@@ -31,10 +31,11 @@
-
-
@@ -114,8 +102,10 @@ 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 { getDamageString, isGlassAvailableForCarId } from "@/helpers/damage-helper";
import { required, regex } from "@/helpers/validation-rules";
import { Form, defineRule } from "vee-validate";
+import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
// DEFINE VALIDATION RULES
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
@@ -159,12 +149,48 @@ export default {
foundWindshieldAlert: false,
vinNotFound: false,
perfectMatchNewVinAlert: false,
- vin: '',
- zip: '',
- email: '',
+ vin: this.getVinFromStore(),
+ zip: this.getZipFromStore(),
+ email: this.getEmailFromStore(),
customAlertData: {},
+ isCarIdDifferent: false,
+ previouslyEnteredCarId: '',
+ invalidZip: '',
};
},
+ computed: {
+ MatchedDifferentVehicleAlertHeader(){
+ let text = this.getCmsContent("MatchedDifferentVehicle",
+ "HeadlineText").replaceAll("{custom:damage}", getDamageString());
+
+ return text;
+ },
+ MatchedDifferentVehicleAlertBody(){
+ let text = this.getCmsContent("MatchedDifferentVehicle",
+ "BodyText").replaceAll("{custom:damage}", getDamageString()).replaceAll("{custom:vinlookupYear}", this.customAlertData?.vehicleInfo?.year).replaceAll("{custom:vinlookupMake}", this.customAlertData?.vehicleInfo?.make).replaceAll("{custom:vinlookupModel}",
+ this.customAlertData?.vehicleInfo?.model);
+
+ return text;
+ },
+ NoServiceZipHeader(){
+ let text = this.getCmsContent("NoServiceZipWidget", "HeadlineText").replaceAll("{custom:serviceZip}", this.invalidZip);
+
+ return text;
+ },
+ NoServiceZipBody(){
+ return this.getCmsContent("NoServiceZipWidget", "BodyText");
+ },
+ PerfectMatchNewVinAlertHeader() {
+ return this.getCmsContent("PerfectMatchNewVinAlert", "HeadlineText");
+ },
+ PerfectMatchNewVinAlertBody() {
+ return this.getCmsContent("PerfectMatchNewVinAlert", "BodyText").replaceAll("{custom:damage}",
+ getDamageString())
+ },
+ isVinFieldReadOnly(){
+ return this.$store.getters.payment.insuranceCoverage.isVerified;
+ },
+ },
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
@@ -176,6 +202,15 @@ export default {
store.commit(storeMutations.UPDATE_REGISTRATION_LAST_NAME, null);
store.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
},
+ getEmailFromStore(){
+ return store.getters.order.customer.emailAddress
+ },
+ getVinFromStore(){
+ return store.getters.vehicle.vin
+ },
+ getZipFromStore(){
+ return store.getters.vehicle.registration.zipCode
+ },
backButtonAction() {
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
},
@@ -187,60 +222,56 @@ export default {
this.customAlertData.zip = this.zip;
this.$refs.funnelFooter.removeLoader();
this.noServiceZip = true;
+ this.invalidZip = this.zip;
return;
}
- const vinLookup = await this.lookupVin(this.vin).catch(() => {
+ const vehicleLookup = await this.lookupVehicle(this.vin).catch(() => {
+ this.vinNotFound = true;
this.$refs.funnelFooter.removeLoader();
- this.noMatchAlert = true;
+ this.noServiceZip = false;
return;
});
- if (vinLookup.data.carId !== store.getters.vehicle.carId) {
- this.customAlertData.vehicleInfo = vinLookup.data.vehicle;
+ this.isCarIdDifferent = vehicleLookup.data.carId !== store.getters.vehicle.carId;
+
+ if (this.isCarIdDifferent && (vehicleLookup.data.carId !== this.previouslyEnteredCarId)) {
+ this.previouslyEnteredCarId = vehicleLookup.data.carId;
+ this.noServiceZip = false;
+ this.customAlertData.vehicleInfo = vehicleLookup.data;
+ this.$refs.funnelFooter.updateButtonText(`Continue with ${vehicleLookup.data.year} ${vehicleLookup.data.make} ${vehicleLookup.data.model}`);
+ this.isVinValid = true;
+ this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(vehicleLookup.data.carId);
this.$refs.funnelFooter.removeLoader();
- this.foundWindshieldAlert = true;
+ this.matchedDifferentVehicle = true;
return;
}
- const carInfo = this.vinDoesNotMatchCarId ? vinLookup.data : store.getters.vehicle;
- this.updateStore(carInfo)
- 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);
+ this.updateStore(vehicleLookup.data);
+ this.navigateForward();
},
- 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);
+ navigateForward(){
+ if(this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){
+ this.$router.navigateAfterSave(this.navigationScenarios.CLICKED_FORWARD, this.$route, {}, { displayVehicleChangeAlert: true }, {});
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);
+ navigateAfterSaveToHeritageFunnel(this.$route);
+ return;
}
},
validateZip(zip) {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ return baseMixin.methods.dispatchStoreAction(
storeActions.VALIDATE_ZIP,
{ zip }
);
},
- lookupVin(vin) {
- return baseMixin.methods.dispatchNonBlockingStoreAction(
+ lookupVehicle(vin) {
+ return baseMixin.methods.dispatchStoreAction(
storeActions.LOOKUP_VEHICLE_BY_VIN,
{ vin }
);
},
updateStore(carInfo) {
- // if(vehicleDamage){
- // store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
- // }
+ if(this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle){
+ store.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
+ }
store.commit(storeMutations.UPDATE_VEHICLE_VIN, this.vin);
store.commit(storeMutations.UPDATE_YEAR, carInfo.year);
store.commit(storeMutations.UPDATE_MAKE, carInfo.make);
@@ -250,16 +281,11 @@ export default {
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, carInfo.category);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, carInfo.imageUrl);
store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, carInfo.imageVifNumber);
- store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, carInfo.imageColor);
+ store.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, carInfo.imageVifNumber);
store.commit(storeMutations.UPDATE_SERVICE_LOCATION_ZIP, this.zip);
store.commit(storeMutations.UPDATE_CUSTOMER_EMAIL_ADDRESS, this.email);
},
},
- computed: {
- isVinFieldReadOnly(){
- return this.$store.getters.payment.insuranceCoverage.isVerified;
- }
- },
components: {
Form,
funnelHeader,
diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js
index 410c39342..c2c86c2dd 100644
--- a/src/mixins/analytics-mixin.js
+++ b/src/mixins/analytics-mixin.js
@@ -28,7 +28,7 @@ export default {
payload.customEvent = { category: category, action: action, label: label, value: value };
}
- baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOG_ACTIVITY, payload, false);
+ baseMixin.methods.dispatchStoreAction(storeActions.LOG_ACTIVITY, payload, false);
},
pushEventToGA(category, action, label, pushToLogApp = false) {
diff --git a/src/mixins/analytics-mixin.spec.js b/src/mixins/analytics-mixin.spec.js
index e79a64882..b4ec2a5b5 100644
--- a/src/mixins/analytics-mixin.spec.js
+++ b/src/mixins/analytics-mixin.spec.js
@@ -16,7 +16,7 @@ describe("analyticsMixin.js", () => {
analyticsMixin.methods.logEvent(type, payload);
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toBeCalled();
+ expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled();
});
test("pushEventToGA, should call logEvent too", () => {
@@ -32,7 +32,7 @@ describe("analyticsMixin.js", () => {
analyticsMixin.methods.pushEventToGA('category', 'action', 'label', true);
// Assert
- expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toBeCalled();
+ expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled();
});
diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js
index f3b29ca17..d8df53281 100644
--- a/src/mixins/base-mixin.js
+++ b/src/mixins/base-mixin.js
@@ -3,6 +3,7 @@ import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
import { vehicleCategories } from "@/constants/vehicle-categories.js";
+import { routerParams } from "@/router/router-constants/router-params";
import { queryStrings } from "@/constants/query-strings";
export default {
@@ -18,7 +19,7 @@ export default {
getCmsContent(widgetName, fieldName) {
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : '';
},
- dispatchNonBlockingStoreAction(type, payload, encodePayload = true) {
+ dispatchStoreAction(type, payload, encodePayload = true) {
// Encode the payload if required
if (encodePayload) {
encodeUriData(payload);
@@ -55,6 +56,9 @@ export default {
vehicleCategories() {
return vehicleCategories;
},
+ routerParams() {
+ return routerParams;
+ },
queryStrings(){
return queryStrings;
}
diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js
index 48af9a7c0..b0c8b26f3 100644
--- a/src/mixins/base-mixin.spec.js
+++ b/src/mixins/base-mixin.spec.js
@@ -6,22 +6,22 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
import store from "@/store";
describe("baseMixin.js", () => {
- test("dispatchNonblockingStoreAction: calls dispatch with type and payload", () => {
+ test("dispatchStoreAction: calls dispatch with type and payload", () => {
const mixIn = getMixInInstance({});
const type = "";
const payload = {};
- mixIn.methods.dispatchNonBlockingStoreAction(type, payload);
+ mixIn.methods.dispatchStoreAction(type, payload);
expect(store.dispatch).toBeCalledWith(type, payload);
});
- test("dispatchNonblockingStoreAction: calls dispatch with type and payload, handles Uri encode", () => {
+ test("dispatchStoreAction: calls dispatch with type and payload, handles Uri encode", () => {
const mixIn = getMixInInstance({});
const type = "";
const payload = { make: "Alfa Romeo/Chrysler" };
- mixIn.methods.dispatchNonBlockingStoreAction(type, payload, true);
+ mixIn.methods.dispatchStoreAction(type, payload, true);
expect(store.dispatch).toBeCalledWith(type, payload);
});
diff --git a/src/router/index.js b/src/router/index.js
index f88d803ef..9ffd28522 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -126,7 +126,7 @@ const router = createRouter({
router.afterEach(async (to, from) => {
// Push page view to GA
analyticsMixin.methods.pushPageViewToGA(to.query[queryStrings.FMG_PAGE]);
- const assignedExperiments = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.GET_EXPERIMENTS_BY_USER_FOR_GA, { userId: getDeviceIdValue() });
+ const assignedExperiments = await baseMixin.methods.dispatchStoreAction(storeActions.GET_EXPERIMENTS_BY_USER_FOR_GA, { userId: getDeviceIdValue() });
analyticsMixin.methods.pushExperimentsToDataLayer(assignedExperiments);
});
diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js
index 57f07a041..5380a3fa7 100644
--- a/src/router/router-constants/navigation-scenarios.js
+++ b/src/router/router-constants/navigation-scenarios.js
@@ -13,7 +13,9 @@ const navigationScenarios = {
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"
+ CONTINUING_WITH_SINGLE_PART: "CONTINUING_WITH_SINGLE_PART",
+ CONTINUING_WITH_MULTIPLE_VEHICLES: "CONTINUING_WITH_MULTIPLE_VEHICLES",
+ TEMPORARY_TO_ADDRESS_LOOKUP: "TEMPORARY_TO_ADDRESS_LOOKUP",
};
export { navigationScenarios };
diff --git a/src/router/router-constants/router-params.js b/src/router/router-constants/router-params.js
new file mode 100644
index 000000000..80ddb779d
--- /dev/null
+++ b/src/router/router-constants/router-params.js
@@ -0,0 +1,5 @@
+const routerParams = {
+ DISPLAY_VEHICLE_CHANGE_ALERT: "displayVehicleChangeAlert"
+ };
+
+ export { routerParams };
\ No newline at end of file
diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js
index a4692627e..3290c43eb 100644
--- a/src/router/router-constants/routing-table.js
+++ b/src/router/router-constants/routing-table.js
@@ -67,12 +67,16 @@ const routingTable = [
},
{
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_MULTIPLE_PARTS,
- destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
+ destinationFmgPageValue: fmgPageValues.VIN_LOOKUP,
},
{
scenario: navigationScenarios.SELECTED_DAMAGE_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.LICENSE_PLATE_LOOKUP,//This might be temporary
},
+ {
+ scenario: navigationScenarios.TEMPORARY_TO_ADDRESS_LOOKUP,
+ destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP, //This will be temporary
+ },
],
},
{
@@ -120,6 +124,10 @@ const routingTable = [
scenario: navigationScenarios.VIN_LOOKUP,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
},
+ {
+ scenario: navigationScenarios.CLICKED_FORWARD,
+ destinationFmgPageValue: fmgPageValues.ESTIMATE,
+ }
],
},
{
@@ -147,6 +155,23 @@ const routingTable = [
},
],
},
+ {
+ fmgPageValue: fmgPageValues.ADDRESS_LOOKUP,
+ maps: [
+ {
+ scenario: navigationScenarios.CLICKED_BACK,
+ destinationFmgPageValue: fmgPageValues.ESTIMATE,
+ },
+ {
+ scenario: navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES,
+ destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
+ },
+ {
+ scenario: navigationScenarios.CLICKED_FORWARD,
+ destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
+ },
+ ],
+ },
];
export { routingTable };
diff --git a/src/store/index.js b/src/store/index.js
index 5b63b2740..66005abd5 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -138,8 +138,8 @@ export const mutations = {
updateRegistrationState(state, registrationState){
state.order.vehicle.registration.state = registrationState;
},
- updateRegistrationZipCode(state, reistrationZipCode){
- state.order.vehicle.registration.zipCode = reistrationZipCode;
+ updateRegistrationZipCode(state, registrationZipCode){
+ state.order.vehicle.registration.zipCode = registrationZipCode;
},
updateRegistrationAddress(state, registrationAddress){
state.order.vehicle.registration.address = registrationAddress;
@@ -298,6 +298,18 @@ export const actions = {
},
});
},
+ lookupVinByAddress(context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState }) {
+ return globalMethods.callHttpClient({
+ method: endpoints.LookupVinByAddress.method,
+ endpoint: endpoints.LookupVinByAddress.url,
+ payload: {
+ licenseLastName: licenseLastName,
+ licenseStreetAddress: licenseStreetAddress,
+ licenseZip: licenseZip,
+ licenseState: licenseState
+ },
+ });
+ },
getVehicleMakes(context, { year }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method,
@@ -454,7 +466,7 @@ export const actions = {
payload: {}
});
},
-
+
// Parts API Actions
getPartsOrQuestions(context, { carId, glassArray, zipCode, vin = '' }) {
diff --git a/vue.config.js b/vue.config.js
index b58adc020..99c9cd754 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,4 +1,4 @@
-process.env.VUE_APP_CONSUMER_API_GATEWAY =
+process.env.VUE_APP_CONSUMER_CF_DISTRO =
"https://consumerapidev.safelite.com";
process.env.VUE_APP_HERITAGE_FUNNEL =
"http://localhost:38000/default.aspx";
diff --git a/vue.release.config.js b/vue.release.config.js
index bb6a6194e..8c705b074 100644
--- a/vue.release.config.js
+++ b/vue.release.config.js
@@ -1,4 +1,4 @@
-process.env.VUE_APP_CONSUMER_API_GATEWAY = "__VUE_APP_CONSUMER_API_GATEWAY__";
+process.env.VUE_APP_CONSUMER_CF_DISTRO = "__VUE_APP_CONSUMER_CF_DISTRO__";
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__";