From f48d21f00dadd99e9b7d70ab5b79db5f321c7df5 Mon Sep 17 00:00:00 2001 From: Katie Date: Tue, 7 Jun 2022 16:07:58 -0400 Subject: [PATCH] Add address-questions tests --- jest.config.js | 1 - .../address-questions.spec.js | 554 +++++++++++++++--- .../address-questions/address-questions.vue | 38 +- 3 files changed, 484 insertions(+), 109 deletions(-) diff --git a/jest.config.js b/jest.config.js index 0dd06d9be..883e588f5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,7 +22,6 @@ module.exports = { // TODO 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/ux-components/alert\alert.vue", "!src/helpers/validation-rules.js", "!src/common-components/menu-modal/menu-modal.vue", diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js index ec50cb30f..ecdb4e791 100644 --- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js +++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js @@ -1,53 +1,27 @@ // Components import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions"; import alert from "@/ux-components/alert/alert"; -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 textboxQuestion from "@/common-components/textbox-question/textbox-question"; -import router from "@/router"; // Supporting Files -import { settleAllPromises } from "@/helpers/layout-helper.js"; -import baseMixin from "@/mixins/base-mixin"; -import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { mount, shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { storeActions } from "@/constants/store-actions"; import { storeMutations } from "@/constants/store-mutations"; import store from "@/store"; -// let fillInAddressFunction; - -// 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(), -// })); - -// jest.mock("@/mixins/base-mixin", () => { -// getCmsContent: jest.fn() -// }) - -// jest.mock("@/helpers/damage-helper", () => ({ -// isGlassAvailableForCarId: jest.fn().mockImplementation(() => true), -// getDamageString: jest.fn() -// })); - -// jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({ -// navigateAfterSaveToHeritageFunnel: jest.fn() -// })); - +let autocompleteElement; describe("address-questions.vue", () => { + beforeEach(() => { + // Create the `addressField1` element (autocomplete's input) + autocompleteElement = document.createElement("input") + autocompleteElement.getPlace = jest.fn(); + document.getElementById = jest.fn().mockReturnValue(autocompleteElement); + }) + describe("initial state", () => { test("only street address field is shown", () => { // Arrange - const { wrapper } = setupMocks(); + const { wrapper } = setupMocks({}); // Assert const streetAddressField = wrapper.findComponent({ ref: "autocomplete" }); @@ -70,27 +44,467 @@ describe("address-questions.vue", () => { }); describe("happy paths", () => { - test.only("street address is entered, user chooses from autocomplete results => other fields are filled in", async () => { - // Arrange - // Create the `addressField1` element (autocomplete's input) - let autocompleteElement = document.createElement("input") - autocompleteElement.getPlace = jest.fn(); - document.getElementById = jest.fn().mockReturnValue(autocompleteElement); + test("full street address is passed in => address fields are displayed", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "12345 Test Road", + city: "Tests", + state: "OH", + zipCode: "12312" + } + } + }); - const { wrapper } = setupMocks({ autocompleteElement: autocompleteElement }); + await wrapper.vm.$nextTick(); + + // Assert + const cityField = wrapper.findComponent({ ref: "city" }); + const stateField = wrapper.findComponent({ ref: "state" }); + const zipField = wrapper.findComponent({ ref: "zipCode" }); + expect(cityField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(stateField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + expect(zipField.exists()).toBeTruthy(); + expect(cityField.isVisible()).toBeTruthy(); + }); + + test("full street address is passed in => don't load Google Autocomplete script", async () => { + // Arrange/Act + const { wrapper } = setupMocks({ + props: { + modelValue: { + streetAddress: "12345 Test Road", + city: "Tests", + state: "OH", + zipCode: "12312" + } + } + }); + + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.$loadScript).not.toHaveBeenCalled(); + }); + + test("address field is focused => disable autocomplete", async () => { + // Arrange + let focusEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "focus") { + focusEventCallbackFunction = callbackFunction; + } + }); + const { wrapper } = setupMocks({}); + await wrapper.vm.$nextTick(); + + // Act + focusEventCallbackFunction(); + await wrapper.vm.$nextTick(); + + // Assert + expect(autocompleteElement.getAttribute("autocomplete")).toEqual("do-not-autofill"); + }); + + test("street address is entered, user chooses good result from autocomplete results => other fields are filled in", async () => { + // Arrange + const { wrapper } = setupMocks({}); await wrapper.setData({ addressModel: { streetAddress: "123 Test Street" } }) + const selectedPlace = { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] + }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + // Act - autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: "boogly" })); + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + + // Assert + const addressModel = wrapper.vm.addressModel; + expect(addressModel.streetAddress).toEqual("1234 Test Road"); + expect(addressModel.city).toEqual("Columbus"); + expect(addressModel.state).toEqual("OH"); + expect(addressModel.zipCode).toEqual("43215"); }) + + test("street address is entered, user chooses good result from autocomplete results => alerts are cleared", async () => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + addressModel: { + streetAddress: "123 Test Street" + }, + displayVerificationWarning: true, + displayNoMatchWarning: true + }) + + let alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.isVisible()).toBeTruthy()); + + const selectedPlace = { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] + }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + + // Act + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + await wrapper.vm.$nextTick(); + + // Assert + alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.exists()).toBeFalsy()); + }); + + test("street address is entered, but user clicks away => first result is selected and other fields are filled in", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({ + querySelectorFunction: function (query) { + if (query == ".pac-container .pac-item") { + let element = document.createElement("div"); + element.textContent = "123 Test Street" + return element; + } + }, + geocoderResult: { + address_components: [ + { + long_name: "1234", + short_name: "1234", + types: ["street_number"] + }, + { + long_name: "Test Road", + short_name: "Test Road", + types: ["route"] + }, + { + long_name: "East Columbus", + short_name: "Columbus", + types: ["neighborhood", "political"] + }, + { + long_name: "Columbus", + short_name: "Columbus", + types: ["locality", "political"] + }, + { + long_name: "Franklin County", + short_name: "Franklin County", + types: ["administrative_area_level_2", "political"] + }, + { + long_name: "Ohio", + short_name: "OH", + types: ["administrative_area_level_1", "political"] + }, + { + long_name: "United States", + short_name: "US", + types: ["country", "political"] + }, + { + long_name: "43215", + short_name: "43215", + types: ["postal_code"] + }, + ] + } + }); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + expect(verificationAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + const addressModel = wrapper.vm.addressModel; + expect(addressModel.streetAddress).toEqual("1234 Test Road"); + expect(addressModel.city).toEqual("Columbus"); + expect(addressModel.state).toEqual("OH"); + expect(addressModel.zipCode).toEqual("43215"); + }); + }); + + describe("alerts", () => { + const places = [null, { address_components: null }, undefined, {}]; + test.each(places)("selected place/place properties is null => display verification alert", async (place) => { + // Arrange + const { wrapper } = setupMocks({}); + await wrapper.setData({ + addressModel: { + streetAddress: "123 Test Street" + }, + displayVerificationWarning: true, + displayNoMatchWarning: true + }) + + let alerts = wrapper.findAllComponents(alert); + alerts.forEach(alert => expect(alert.isVisible()).toBeTruthy()); + + const selectedPlace = place; + + // Act + autocompleteElement.dispatchEvent(new CustomEvent("place_changed", { detail: selectedPlace })); + await wrapper.vm.$nextTick(); + + // Assert + const verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(verificationAlert.exists()).toBe(true); + expect(verificationAlert.isVisible()).toBe(true); + const noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBe(false); + }); + + test("user enters address that yields no autocomplete results => show noMatch alert", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({}); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeTruthy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); + }); + + test("user enters address that yields autocomplete results, but doesn't select => show verification alert", async () => { + // Arrange + let changeEventCallbackFunction; + autocompleteElement.addEventListener = jest.fn().mockImplementation((eventName, callbackFunction) => { + if (eventName == "change") { + changeEventCallbackFunction = callbackFunction; + } + }); + + const { wrapper } = setupMocks({ + querySelectorFunction: function (query) { + if (query == ".pac-container .pac-item") { + let element = document.createElement("div"); + element.textContent = "123 Test Street" + return element; + } + } + }); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + expect(verificationAlert.exists()).toBeFalsy(); + + await wrapper.vm.$nextTick(); + + // Act + changeEventCallbackFunction(); + + await wrapper.vm.$nextTick(); + + // Assert + verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + expect(wrapper.vm.displayVerificationWarning).toBeTruthy(); + expect(verificationAlert.exists()).toBeTruthy(); + expect(verificationAlert.isVisible()).toBeTruthy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + + describe("noMatch alert is cleared on address change", () => { + test("user sees noMatch warning and enters city => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + wrapper.setData({ + displayNoMatchWarning: true + }); + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); + + // Act + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + city: "Somewhere" + }) + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + + test("user sees noMatch warning and enters state => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + wrapper.setData({ + displayNoMatchWarning: true + }); + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); + + // Act + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + state: "KO" + }) + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + + test("user sees noMatch warning and enters zip code => noMatch warning is removed", async () => { + // Arrange + const { wrapper } = setupMocks({}); + + wrapper.setData({ + displayNoMatchWarning: true + }); + await wrapper.vm.$nextTick(); + + let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeTruthy(); + expect(noMatchAlert.isVisible()).toBeTruthy(); + + // Act + wrapper.vm.$options.watch.addressModel.handler.call(wrapper.vm, { + zipCode: "12345" + }) + await wrapper.vm.$nextTick(); + + // Assert + expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); + noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + expect(noMatchAlert.exists()).toBeFalsy(); + }); + }); }); }); -function setupMocks({ mountOptions, selectedPlace, isShallowMount = true }) { +function setupMocks({ mountOptions, props, isShallowMount = true, querySelectorFunction, geocoderResult = ["1234 Test Street"] }) { store.commit(storeMutations.RESET_STATE); const resultingMountOptions = getMountOptions({ @@ -102,59 +516,49 @@ function setupMocks({ mountOptions, selectedPlace, isShallowMount = true }) { loadScript: jest.fn().mockResolvedValue() }); - // document.getElementById = jest.fn().mockImplementation(id => { - // if (id == "autocomplete") { - // console.log("get it") - // return autocompleteElement; - // } - // }) - - // fillInAddressFunction = null; - - - window.google = { maps: { event: { addListener: jest.fn().mockImplementation((element, eventName, callbackFunction) => { - console.log("Here") - // console.log(element) - // console.log(eventName) - // console.log(callbackFunction) - // element.addEventListener(eventName, callbackFunction); function interceptedCallbackFunction(e) { - console.log("intercepted") - console.log(e) callbackFunction(e.detail); } // selectedPlace = "Woogly"; element.addEventListener(eventName, interceptedCallbackFunction); - // fillInAddressFunction = callbackFunction; }), removeListener: jest.fn(), clearInstanceListeners: jest.fn() }, places: { Autocomplete: jest.fn().mockImplementation((el) => el) + }, + Geocoder: class Geocoder { + // constructor(); + + geocode(request, callback) { + callback([geocoderResult], true) + } + }, + GeocoderStatus: { + OK: true } } }; + if (props) + resultingMountOptions.propsData = props; const wrapper = isShallowMount ? shallowMount(addressQuestions, resultingMountOptions) : mount(addressQuestions, resultingMountOptions); - // const wrapper = shallowMount(addressQuestions, resultingMountOptions); - // wrapper.findComponent({ ref: "autocomplete" }).element = autocompleteElement; - document.querySelector = jest.fn().mockReturnValue(document.createElement("div")); + document.querySelector = jest.fn().mockImplementation(query => { + let result = null; + if (query == ".pac-container") + result = document.createElement("div"); + else if (querySelectorFunction) { + result = querySelectorFunction(query); + } - // console.log(wrapper.$loadScript) - // console.log(wrapper.vm.$loadScript) - // wrapper.vm.$loadScript = jest.fn().mockImplementation(resolve => new Promise(resolve({}))); - - // wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); - // wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent; - // wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn(); - // wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn(); - // wrapper.vm.$refs.loadingModal.showModal = jest.fn(); + return result ?? null; + }); return { wrapper }; } \ No newline at end of file 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 4a61da56b..d93c2559b 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 @@ -185,13 +185,10 @@ export default ({ }, methods: { setupAddressLookup() { - console.log("setup") - console.log(this.addressModel) if (this.addressModel.streetAddress && this.addressModel.city && this.addressModel.state && this.addressModel.zipCode) { - console.log("AHHHH") this.showAddressFields = true; return; @@ -204,7 +201,6 @@ export default ({ this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`) .then(() => { - console.log("A") // Script is loaded, initialize the autocomplete textbox const autocomplete = new window.google.maps.places.Autocomplete( addressField1, @@ -215,19 +211,9 @@ export default ({ } ); - console.log("B") // Standard place_changed event handling const autocompleteListener = window.google.maps.event.addListener(autocomplete, 'place_changed', fillInAddress); - console.log("C") - // console.log(addressField1) - // console.log(addressField1.id) - // console.log(autocomplete) - // console.log(autocomplete.id) - // console.log(addressField1 == autocomplete) - - - // Wrapping the addressField1 element in the Google Address Autocomplete object // will cause "autocomplete='off'" which Chrome completely ignores. This event // handler will set the value to something arbitrary so autofill doesn't work. @@ -243,9 +229,7 @@ export default ({ } }) - console.log("D") - - addressField1.onchange = function() { + addressField1.addEventListener("change", () => { 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) { @@ -272,12 +256,9 @@ export default ({ self.displayNoMatchWarning = true; } } - }; + }); - console.log("E") function fillInAddress(place) { - console.log("fillInAddress") - console.log(place) if (!place) { place = autocomplete.getPlace(); } @@ -327,10 +308,7 @@ export default ({ addressField1.onchange = null; const pacContainer = document.querySelector(".pac-container"); pacContainer.remove(); - } - - console.log("F") }) .catch(() => { // Failed to fetch script @@ -344,16 +322,10 @@ export default ({ watch: { addressModel: { handler(newValue) { - // The first time the address model changes is w - if (!newValue.city && - !newValue.state && - !newValue.zipCode) { - return; - } - - if (this.displayNoMatchWarning === true) { + // Clear no match warning on address change + if (newValue.city || newValue.state || newValue.zipCode) { this.displayNoMatchWarning = false; - } + } }, deep: true }