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/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js
index 591b34317..b70304035 100644
--- a/src/common-components/button-question/button-question.spec.js
+++ b/src/common-components/button-question/button-question.spec.js
@@ -1,9 +1,8 @@
import { shallowMount } from "@vue/test-utils";
import buttonQuestion from "@/common-components/button-question/button-question";
-import { nextTick } from "vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
-import store from "@/store";
-jest.mock("@/store",()=>{return{};},{virtual:true});
+
+jest.mock("@/store", () => { return {}; }, { virtual: true });
describe("buttonQuestion.vue", () => {
it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => {
@@ -76,7 +75,7 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("getColLength should return '' if prop isWide is set to false", () => {
// Act
- const localThis = {
+ const localThis = {
isWide: false,
answers: ['a', 'b']
}
@@ -110,12 +109,12 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("Should trigger event modelValue change to new value on when radio button selected", async () => {
// Act
- const wrapper = shallowMount(buttonQuestion);
+ const wrapper = shallowMount(buttonQuestion, setupMocks({}));
await wrapper.setProps({
answers: ["2022", "2021", "2020"],
isMultiSelect: false
});
- const val = {checkValue: true, value: "2021", }
+ const val = { checkValue: true, value: "2021", }
wrapper.vm.handleCheckedChanged(val);
// Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]);
@@ -125,13 +124,13 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("Should add values to array on checkbox click", () => {
// Act
- const wrapper = shallowMount(buttonQuestion, {
+ const wrapper = shallowMount(buttonQuestion, setupMocks({
propsData: {
modelValue: ["2022", "2021", "2020"],
isMultiSelect: true,
}
- });
- const val = {checkValue: true, value: "2019", }
+ }));
+ const val = { checkValue: true, value: "2019", }
wrapper.vm.handleCheckedChanged(val);
// Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]);
@@ -141,12 +140,12 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => {
// Act
- const wrapper = shallowMount(buttonQuestion, {
+ const wrapper = shallowMount(buttonQuestion, setupMocks({
propsData: {
- isMultiSelect: true,
- modelValue: [ 'a', 'b' ]
+ isMultiSelect: true,
+ modelValue: ['a', 'b']
}
- });
+ }));
const val = { checkValue: true, value: "2021", }
wrapper.vm.handleCheckedChanged(val);
@@ -158,12 +157,13 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => {
// Act
- const wrapper = shallowMount(buttonQuestion, {
+ const wrapper = shallowMount(buttonQuestion, setupMocks({
propsData: {
- isMultiSelect: true,
- modelValue: [ 'a', 'b' ]
+ isMultiSelect: true,
+ modelValue: ['a', 'b']
}
- });
+ }));
+
const val = { checkValue: false, value: "a", }
wrapper.vm.handleCheckedChanged(val);
@@ -176,12 +176,12 @@ describe("buttonQuestion.vue", () => {
describe("buttonQuestion.vue", () => {
it("Should do nothing to this.selectedValues if this.selectedValues is not an array", () => {
// Act
- const wrapper = shallowMount(buttonQuestion, {
+ const wrapper = shallowMount(buttonQuestion, setupMocks({
propsData: {
- isMultiSelect: true,
+ isMultiSelect: true,
modelValue: 'a',
}
- });
+ }));
const val = { checkValue: true, value: "c", }
wrapper.vm.handleCheckedChanged(val);
@@ -189,3 +189,11 @@ describe("buttonQuestion.vue", () => {
expect(wrapper.vm.selectedValues).toEqual("a");
});
});
+
+function setupMocks(mountOptionsMockData = {}) {
+ const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } };
+ const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
+ const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
+
+ return allMountOptions;
+}
diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue
index 48bbe9227..7fba59c51 100644
--- a/src/common-components/button-question/button-question.vue
+++ b/src/common-components/button-question/button-question.vue
@@ -52,6 +52,7 @@ import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-bu
import listCard from "@/ux-components/list-card/list-card";
import { ErrorMessage } from 'vee-validate';
import radio from "@/ux-components/radio/radio";
+import { queryStrings } from "@/constants/query-strings";
export default {
name: "buttonQuestion",
@@ -133,6 +134,9 @@ export default {
return answer.Name ? answer.Name : answer;
},
handleCheckedChanged(val) {
+
+ this.pushEventToGA(this.$route.query[queryStrings.FMG_PAGE], this.GaActions.CLICKED, val.value, true);
+
if(this.isMultiSelect && this.selectedValues) {
// Add or remove item to array of data to emit
const newSelectedValues = this.selectedValues;
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/analytics-page-events.js b/src/constants/analytics-page-events.js
deleted file mode 100644
index 74ff7cc2f..000000000
--- a/src/constants/analytics-page-events.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const analyticsPageEvents = {
- ENTRY: "ENTRY",
- EVENT: "EVENT"
-};
-
-export { analyticsPageEvents };
diff --git a/src/constants/analytics.js b/src/constants/analytics.js
new file mode 100644
index 000000000..f6f6ff82e
--- /dev/null
+++ b/src/constants/analytics.js
@@ -0,0 +1,32 @@
+const analyticsPageEvents = {
+ ENTRY: "ENTRY",
+ EVENT: "EVENT"
+};
+
+// GA Constants
+const GaEvents = {
+ GENERIC_EVENT: 'ga_Event',
+ PAGE_VIEW_EVENT : 'logPageview'
+};
+
+const GaCategories = {
+ API_RESPONSE: 'Api_Response',
+ EVOX: 'Evox'
+};
+
+const GaActions = {
+ RESULT: 'Result',
+ CLICKED: 'Clicked',
+ VIF: 'vif',
+ SUBMITTED: 'Submitted',
+};
+
+const GaLabels = {
+ SUCCESS: 'Success',
+ ERROR: 'Error',
+ LICENSE_PLATE_LOOKUP: 'License_Plate_Look_Up',
+ VIN_LOOKUP: 'Vin_Look_Up',
+};
+
+
+export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents};
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 322dab574..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",
@@ -70,6 +74,10 @@ const endpoints = {
LogActivity:{
url: "/analytics/api/v1/analytics/activity",
method: "POST",
+ },
+ GetExperimentsByUserForGa: {
+ url: "/analytics/api/v1/analytics/get-experiments-for-GA",
+ method: "GET",
}
};
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/experiments.js b/src/constants/experiments.js
index 13a51aa7f..cd2325a24 100644
--- a/src/constants/experiments.js
+++ b/src/constants/experiments.js
@@ -1,6 +1,10 @@
const experimentUniverses = {
CONCEPT_FUNNEL: 'ConceptFunnel'
};
+
+const experimentSettings = {
+ GOOGLE_CUSTOM_DIMENSION_INDEX: 'Google Custom Dimension Index'
+}
-export { experimentUniverses };
+export { experimentUniverses, experimentSettings};
\ No newline at end of file
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index 7587907dc..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",
@@ -19,6 +20,7 @@ const storeActions = {
VALIDATE_ZIP: "validateZip",
LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure",
LOG_ACTIVITY: "logActivity",
+ GET_EXPERIMENTS_BY_USER_FOR_GA: "getExperimentsByUserForGa",
// DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies",
diff --git a/src/global-methods.js b/src/global-methods.js
index c0823f29a..5e4d04ea3 100644
--- a/src/global-methods.js
+++ b/src/global-methods.js
@@ -1,39 +1,34 @@
import axios from "axios";
+import analyticsMixIn from "@/mixins/analytics-mixin.js";
+
import { applicationConfig } from "@/constants/application-config.js";
-import httpStatusCodes from "http-status-codes";
+import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
export default {
- callHttpClient({ method, endpoint, payload }) {
+ 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" });
- const payloadAndAnalyticsData = Object.assign({}, payload, {
- AppName: "FixMyGlass",
- });
+ axios({ method: method, url: cfDistroUrl + endpoint, data: payloadAndAnalyticsData, crossDomain: true, responseType: {} })
+ .then((response) => {
- axios({
- method: method,
- url: apiGatewayUrl + endpoint,
- data: payloadAndAnalyticsData,
- crossDomain: true,
- responseType: {},
- }).then(
- (response) => {
- if (response.status == httpStatusCodes.OK) {
- if(response.data == undefined) {
- reject(response);
- }else{
- resolve(response);
- }
- } else {
- reject(response);
+ if (logApiCall) {
+ analyticsMixIn.methods.pushEventToGA(GaCategories.API_RESPONSE, GaActions.RESULT, `${GaLabels.SUCCESS}_${endpoint}`, true);
}
+
+ return resolve(response);
},
- (error) => {
- console.error(error);
- return reject(error.response);
- }
- );
+ error => {
+ console.error(error);
+
+ if (logApiCall) {
+ analyticsMixIn.methods.pushEventToGA(GaCategories.API_RESPONSE, GaActions.RESULT, `${GaLabels.ERROR}_${endpoint}`, true);
+ }
+
+ return reject(error.response);
+ }
+ );
});
},
@@ -48,11 +43,7 @@ export default {
responseType: {},
}).then(
(response) => {
- if (response.status == httpStatusCodes.OK) {
- resolve(response);
- } else {
- reject(response);
- }
+ resolve(response);
},
(error) => {
return reject(error.response);
diff --git a/src/global-methods.spec.js b/src/global-methods.spec.js
index 59fa15b77..5dcba0ccd 100644
--- a/src/global-methods.spec.js
+++ b/src/global-methods.spec.js
@@ -1,13 +1,16 @@
import globalMethods from "@/global-methods";
import axios from "axios";
+import analyticsMixIn from "@/mixins/analytics-mixin";
//Mock external dependencies
jest.mock("axios");
+jest.mock("@/mixins/analytics-mixin");
it("Global Methods - Call Http Client - Should Resolve Promise", () => {
//Arrange
const endpoint = "https://mock.safelite.com";
const httpArgs = setupMocksForHttpClient({ endpoint: endpoint });
+ analyticsMixIn.methods.pushEventToGA = jest.fn();
//Act
globalMethods.callHttpClient(httpArgs).then((response) => {
@@ -25,6 +28,7 @@ it("Global Methods - Call Http Client - Should Reject Promise", () => {
endpoint: endpoint,
isError: true,
});
+ analyticsMixIn.methods.pushEventToGA = jest.fn();
//Act
globalMethods.callHttpClient(httpArgs).catch((err) => {
@@ -72,5 +76,6 @@ function setupMocksForHttpClient({
return {
endpoint: endpoint,
+ logApiCall: true
};
}
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 753465886..6577ed6ff 100644
--- a/src/helpers/unit-test-helper.js
+++ b/src/helpers/unit-test-helper.js
@@ -7,17 +7,26 @@ import { cookieNames } from "@/constants/cookie-names";
import { Form } from "vee-validate";
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.dispatchNonBlockingStoreAction = jest.fn();
- mocks.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
+ mocks.pushEventToGA = jest.fn();
+ mocks.pushPageViewToGA = jest.fn();
+ mocks.logEvent = jest.fn();
+ mocks.pushExperimentsToDataLayer = jest.fn();
+ mocks.prependActionToMethod = jest.fn();
+
+ mocks.dispatchStoreAction = jest.fn();
+ mocks.dispatchStoreAction.mockImplementation((actionName) => {
let actionFilterResult = mockData.actionList.filter(
(x) => x.actionName == actionName
);
@@ -35,6 +44,13 @@ export function getMountOptions(mockData) {
mocks.navigationScenarios = navigationScenarios;
mocks.vehicleCategories = vehicleCategories;
mocks.fmgPageValues = fmgPageValues;
+ mocks.analyticsPageEvents = analyticsPageEvents;
+ mocks.GaCategories = GaCategories;
+ mocks.GaActions = GaActions;
+ 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;
@@ -50,7 +66,7 @@ export function getMountOptions(mockData) {
}
export function setupMocksForJsFiles(mockData = {}) {
- setupBaseMixinDispatchNonBlockingStoreAction(mockData);
+ setupBaseMixinDispatchStoreAction(mockData);
return { baseMixin };
}
@@ -90,10 +106,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..103cc61fa 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 { navigateAfterSaveToHeritageFunnel } 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 @@