diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index c44a6fe85..e1cb33e14 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -70,6 +70,7 @@ const storeActions = { SAVE_VIN: "saveVin", SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup", SAVE_GLASS_PARTS: "saveGlassParts", + SAVE_GLASS_PART_PRICES: "saveGlassPartPrices", SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers", RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED: "resetMoldingAndCapabilityQuestionAnswersIfNeeded", @@ -78,6 +79,7 @@ const storeActions = { SAVE_PAYMENT_TYPE: "savePaymentType", SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber", SAVE_SUPPORTING_ITEMS: "saveSupportingItems", + SAVE_SUPPORTING_ITEMS_AND_RESET_SERVICE_LOCATION: "saveSupportingItemsAndResetServiceLocation", SAVE_VAPS: "saveVaps", }; diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 82cc2082d..939caf922 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -58,6 +58,9 @@ const storeMutations = { RESET_GLASS_PARTS_STATE: "resetGlassPartsState", RESET_STATE: "resetState", RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise", + RESET_SERVICE_LOCATION_APPOINTMENT_TYPE: "resetServiceLocationAppointmentType", + RESET_SERVICE_LOCATION_PROVIDER: "resetServiceLocationProvider", + RESET_SERVICE_LOCATION_MOBILE_ADDRESS: "resetServiceLocationMobileAddress", RESET_SCHEDULE: "resetSchedule", // OTHER MUTATIONS diff --git a/src/helpers/object-helper.js b/src/helpers/object-helper.js new file mode 100644 index 000000000..b42230be1 --- /dev/null +++ b/src/helpers/object-helper.js @@ -0,0 +1,81 @@ +// For nested objects, spread operator only creates new references to the top level fields, +// the remaining nested fields actually reference the original object which can introduce problems. + +// The purpose of this method is to deep clone the data in an object recursively, this is useful +// for cloning modelValues to internal models when regular two-way binding is not an option. +// See: mobile-location-modal-questions.vue + +// Creates a deep clone of an object. Clones primitives, arrays and objects, excluding class instances. +// https://www.30secondsofcode.org/js/s/deep-clone +export function deepClone(object) { + if (object === null) { + return null; + } + + let clone = Object.assign({}, object); + Object.keys(clone).forEach( + (key) => + (clone[key] = typeof object[key] === "object" ? deepClone(object[key]) : object[key]) + ); + + if (Array.isArray(object)) { + clone.length = object.length; + return Array.from(clone); + } + + return clone; +} + +// The purpose of this method is to check for array or object equality recursively to determine if two complex objects are equal. +// This is only a comparison of data, not functions. +export function deepEqual(obj1, obj2) { + if (typeof obj1 !== typeof obj2) { + return false; + } + + if (obj1 === null || obj2 === null) { + return obj1 === obj2; + } + + if (Array.isArray(obj1) && Array.isArray(obj2)) { + if (obj1.length !== obj2.length) { + return false; + } + + const sorted1 = obj1.slice().sort(); + const sorted2 = obj2.slice().sort(); + + for (let i = 0; i < sorted1.length; i++) { + if (!deepEqual(sorted1[i], sorted2[i])) { + return false; + } + } + + return true; + } + + if (typeof obj1 === "object" && typeof obj2 === "object") { + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) { + return false; + } + + const sortedKeys1 = keys1.sort(); + const sortedKeys2 = keys2.sort(); + + for (let i = 0; i < sortedKeys1.length; i++) { + const key1 = sortedKeys1[i]; + const key2 = sortedKeys2[i]; + + if (key1 !== key2 || !deepEqual(obj1[key1], obj2[key2])) { + return false; + } + } + + return true; + } + + return obj1 === obj2; +} diff --git a/src/helpers/object-helper.spec.js b/src/helpers/object-helper.spec.js new file mode 100644 index 000000000..730dea1f8 --- /dev/null +++ b/src/helpers/object-helper.spec.js @@ -0,0 +1,275 @@ +import { deepClone, deepEqual } from "./object-helper"; + +describe("object-cloning-helper.js", () => { + describe("deepClone", () => { + it("Should return null if no object is passed in", async () => { + // Arrange + const expected = null; + + // Act + const result = deepClone(null); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return a deep copy of the object", async () => { + // Arrange + + const object = { + addressQuestions: { + streetAddress: "555 Some St", + apartmentNumberOrBusinessName: "Apt 1", + city: "Funkytown", + state: "OH", + zipCode: "55555", + }, + isVehicleProtected: true, + serviceZipCode: "55555", + }; + + const expected = { + addressQuestions: { + streetAddress: "555 Some St", + apartmentNumberOrBusinessName: "Apt 1", + city: "Funkytown", + state: "OH", + zipCode: "55555", + }, + isVehicleProtected: true, + serviceZipCode: "55555", + }; + + // Act + const result = deepClone(object); + + // Assert + expect(result).toStrictEqual(expected); + }); + + it("Should return a copy of the array", async () => { + // Arrange + + const array = [9, 8, 7, 6, 5, 4, 3, 2, 1]; + + const expected = [9, 8, 7, 6, 5, 4, 3, 2, 1]; + + // Act + const result = deepClone(array); + + // Assert + expect(result).toStrictEqual(expected); + }); + }); + + describe("deepEqual", () => { + it("Should return false if the items being compared are not the same type", async () => { + // Arrange + const obj1 = ""; // String + const obj2 = 3; // Number + const expected = false; + + // Act + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return false if one the items being compared is null", async () => { + // Arrange + const obj1 = {}; // Object + const obj2 = null; // Array + const expected = false; + + // Act + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + describe("Both items being compared are arrays", () => { + it("Should return true if the two arrays have the same elements in the same order", async () => { + // Arrange + const obj1 = [1, 2, 3]; // Array + const obj2 = [1, 2, 3]; // Array + const expected = true; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return true if the two arrays have the same elements in a different order", async () => { + // Arrange + const obj1 = [1, 2, 3]; // Array + const obj2 = [3, 1, 2]; // Array + const expected = true; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return false if the two arrays are not the same length", async () => { + // Arrange + const obj1 = [1, 2, 3]; // Array + const obj2 = [1, 2]; // Array + const expected = false; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return false if they have the same elements but their the array elements are different", async () => { + // Arrange + const obj1 = [1, 2, 3]; // Array + const obj2 = [4, 5, 6]; // Array + const expected = false; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + }); + + describe("Both items being compared are objects", () => { + it("Should return false if the two objects *do not* have the same number of keys", async () => { + // Arrange + const obj1 = { + prop1: {}, + }; // Object + + const obj2 = { + prop1: {}, + prop2: {}, + }; // Object + const expected = false; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return true if the two objects have the same keys in the same order", async () => { + // Arrange + const obj1 = { + prop1: {}, + prop2: {}, + }; // Object + + const obj2 = { + prop1: {}, + prop2: {}, + }; // Object + + const expected = true; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return true if the two objects have the same keys in a different order", async () => { + // Arrange + const obj1 = { + prop1: {}, + prop2: {}, + }; // Object + + const obj2 = { + prop2: {}, + prop1: {}, + }; // Object + + const expected = true; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return false if the two objects have the same keys but in a nested object comparison one of the two values is null", async () => { + // Arrange + const obj1 = { + prop1: { + subProp1: { + foo: "", + bar: null, + }, + }, + prop2: { + subProp1: [1, 2, 3], + }, + }; // Object + + const obj2 = { + prop1: {}, + prop2: {}, + }; // Object + + const expected = false; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + + it("Should return false if the two objects have the same elements but a nested object comparison is of two different types", async () => { + // Arrange + const obj1 = { + prop1: { + subProp1: { + foo: "", + bar: "", + }, + }, + prop2: { + subProp1: [1, 2, 3], + }, + }; // Object + + const obj2 = { + prop1: { + subProp1: { + foo: "", + bar: "", + }, + }, + prop2: { + subProp1: { + foo1: "", + bar1: "", + }, + }, + }; // Object + + const expected = false; + + // Acts + const result = deepEqual(obj1, obj2); + + // Assert + expect(result).toEqual(expected); + }); + }); + }); +}); diff --git a/src/layouts/estimate/estimate.vue b/src/layouts/estimate/estimate.vue index 1ca0c9d48..404caff1d 100644 --- a/src/layouts/estimate/estimate.vue +++ b/src/layouts/estimate/estimate.vue @@ -272,7 +272,7 @@ export default { const resultMap = await settleAllPromises(promiseResultMap); this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS, + this.storeActions.SAVE_SUPPORTING_ITEMS_AND_RESET_SERVICE_LOCATION, resultMap.supportingItems, false ); diff --git a/src/layouts/molding-questions/molding-questions.spec.js b/src/layouts/molding-questions/molding-questions.spec.js index 13961df5a..9c003e748 100644 --- a/src/layouts/molding-questions/molding-questions.spec.js +++ b/src/layouts/molding-questions/molding-questions.spec.js @@ -109,7 +109,7 @@ store.getters = { damage: baseStoreGettersDamage, order: {}, }; -store.commit = jest.fn(); +store.dispatch = jest.fn(); afterEach(() => { // reset store after each test diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 06b8ecb9a..4a28151f3 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -182,6 +182,7 @@ export default { this.isInsuranceSelected, false ); + if (!this.isInsuranceSelected) { this.dispatchStoreAction( this.storeActions.SAVE_PARENT_ACCOUNT_NUMBER, @@ -196,18 +197,21 @@ export default { ) { this.supportingItems = this.filterOutFees(this.supportingItems); } + if (this.pricedGlassParts.length > 0) { this.dispatchStoreAction( - this.storeActions.SAVE_GLASS_PARTS, + this.storeActions.SAVE_GLASS_PART_PRICES, this.pricedGlassParts, false ); } + this.dispatchStoreAction( - this.storeActions.SAVE_SUPPORTING_ITEMS, + this.storeActions.SAVE_SUPPORTING_ITEMS_AND_RESET_SERVICE_LOCATION, this.supportingItems, false ); + this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false); const payment = this.$store.getters.payment; diff --git a/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.js b/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.js deleted file mode 100644 index 45899973f..000000000 --- a/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.js +++ /dev/null @@ -1,27 +0,0 @@ -// For nested objects, spread operator only creates new references to the top level fields, -// the remaining nested fields actually reference the original object which can introduce problems. - -// The purpose of this method is to deep clone the data in an object recursively, this is useful -// for cloning modelValues to internal models when regular two-way binding is not an option. -// See: mobile-location-modal-questions.vue - -// Creates a deep clone of an object. Clones primitives, arrays and objects, excluding class instances. -// https://www.30secondsofcode.org/js/s/deep-clone -export function deepClone(object) { - if (object === null) { - return null; - } - - let clone = Object.assign({}, object); - Object.keys(clone).forEach( - (key) => - (clone[key] = typeof object[key] === "object" ? deepClone(object[key]) : object[key]) - ); - - if (Array.isArray(object)) { - clone.length = object.length; - return Array.from(clone); - } - - return clone; -} diff --git a/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.spec.js b/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.spec.js deleted file mode 100644 index 4246f9167..000000000 --- a/src/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import { deepClone } from "./object-cloning-helper"; - -describe("object-cloning-helper.js", () => { - it("Should return null if no object is passed in", async () => { - // Arrange - const expected = null; - - // Act - const result = deepClone(null); - - // Assert - expect(result).toEqual(expected); - }); - - it("Should return a deep copy of the object", async () => { - // Arrange - - const object = { - addressQuestions: { - streetAddress: "555 Some St", - apartmentNumberOrBusinessName: "Apt 1", - city: "Funkytown", - state: "OH", - zipCode: "55555", - }, - isVehicleProtected: true, - serviceZipCode: "55555", - }; - - const expected = { - addressQuestions: { - streetAddress: "555 Some St", - apartmentNumberOrBusinessName: "Apt 1", - city: "Funkytown", - state: "OH", - zipCode: "55555", - }, - isVehicleProtected: true, - serviceZipCode: "55555", - }; - - // Act - const result = deepClone(object); - - // Assert - expect(result).toStrictEqual(expected); - }); - - it("Should return a copy of the array", async () => { - // Arrange - - const array = [9, 8, 7, 6, 5, 4, 3, 2, 1]; - - const expected = [9, 8, 7, 6, 5, 4, 3, 2, 1]; - - // Act - const result = deepClone(array); - - // Assert - expect(result).toStrictEqual(expected); - }); -}); diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue index 78094e76b..0052c1368 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue @@ -66,7 +66,7 @@ import addressQuestions from "@/layouts/address-lookup/customer-questions/addres import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location-modal-questions/vehicle-protected-question/vehicle-protected-question"; // Helpers -import { deepClone } from "@/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper"; +import { deepClone } from "@/helpers/object-helper"; import { getPricedMobileFeePart, getServiceabilityDetails, diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 5e2ec0c71..e3dbb7875 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -1,4 +1,3 @@ -fmg