diff --git a/src/layouts/vehicle-parts/vehicle-parts.spec.js b/src/layouts/vehicle-parts/vehicle-parts.spec.js
new file mode 100644
index 00000000..12292117
--- /dev/null
+++ b/src/layouts/vehicle-parts/vehicle-parts.spec.js
@@ -0,0 +1,408 @@
+// Components
+import vehicleParts from "@/layouts/vehicle-parts/vehicle-parts.vue";
+import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass-part-question";
+
+// Supporting Files
+import { settleAllPromises } from "@/helpers/layout-helper.js";
+import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
+import { shallowMount } from "@vue/test-utils";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import { nextTick } from "vue";
+import baseMixin from "@/mixins/base-mixin.js";
+import { useMainStore } from "@/store";
+import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
+
+
+// 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(),
+}));
+
+
+const basePartResponse = {
+ partsOrQuestions: [
+ {
+ glassName: "Stationary",
+ glassLocation: "Rear",
+ parts: [
+ {
+ partNumber: "DB12209GTYN",
+ description: "heated glass, solar, 1 hole",
+ color: "Green Tint",
+ requiresRecalibration: false,
+ requiresCapabilityQuestions: false,
+ childParts: null,
+ },
+ {
+ partNumber: "DB12209YPYNOEM",
+ description: "heated glass, solar, 1 hole",
+ color: "Gray Tint Privacy",
+ requiresRecalibration: false,
+ requiresCapabilityQuestions: false,
+ childParts: null,
+ },
+ ],
+ partQuestions: null,
+ },
+ ],
+};
+
+describe("vehicle-parts.vue", () => {
+ test("Set cms content called on load", async (done) => {
+ //Arrange
+ useMainStore().pageData = jest.fn();
+ useMainStore().pageData.mockReturnValue(basePartResponse);
+ useMainStore().lineItems = { glassParts: null };
+
+ const { wrapper, apiPromise } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn()
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+ wrapper.vm.setCmsContent = jest.fn();
+
+ //Assert
+ apiPromise.finally(() => {
+ expect(wrapper.vm.setCmsContent).toHaveBeenCalled();
+ done();
+ });
+ });
+
+ test("PageData / isRepair populated in Vuex. arePagePrerequisitesValid should be true ", async () => {
+ //Arrange
+ useMainStore().pageData = jest.fn();
+ useMainStore().pageData.mockReturnValue(basePartResponse);
+ useMainStore().lineItems = { glassParts: null };
+
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { fmgPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
+ await nextTick();
+
+ //Assert
+ expect(arePagePrerequisitesValid).toBe(true);
+ });
+
+ test("Initial data, should populate this.selectedGlassParts", async () => {
+ //Arrange
+ useMainStore().pageData = jest.fn();
+ useMainStore().pageData.mockReturnValue(basePartResponse);
+ useMainStore().lineItems = { glassParts: [{ partNumber: "DB12209YPYNOEM" }] };
+
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ await nextTick();
+
+ //Assert
+ expect(wrapper.vm.selectedGlassParts).toEqual({
+ "Rear-Stationary": {
+ partNumber: "DB12209YPYNOEM",
+ description: "heated glass, solar, 1 hole",
+ color: "Gray Tint Privacy",
+ requiresRecalibration: false,
+ requiresCapabilityQuestions: false,
+ childParts: null,
+ },
+ });
+ });
+
+ test("User had part questions > navigateBack triggers a router.navigateWithoutSaving change with correct scenario", async () => {
+ //Arrange
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ wrapper.vm.navigateBack();
+
+ //Assert
+ expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
+ navigationScenarios.CLICKED_BACK_WITH_PART_QUESTIONS,
+ wrapper.vm.$route
+ );
+ });
+
+ test("User did not have part questions > navigateBack triggers a router.navigate change with correct scenario", async () => {
+ //Arrange
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ wrapper.vm.navigateBack();
+
+ //Assert
+ expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
+ navigationScenarios.CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS,
+ wrapper.vm.$route
+ );
+ });
+
+ test("ForwardButtonAction triggers a router.navigate change if there are child part questions", async () => {
+ //Arrange
+ useMainStore().pageData = jest.fn();
+ useMainStore().pageData.mockReturnValue({
+ partsOrQuestions: [
+ {
+ glassName: "Single",
+ glassLocation: "Windshield",
+ parts: [
+ {
+ partNumber: "FW03861GTYN",
+ description:
+ "rain sensor, heated glass, auto dimming mirror, solar, 3rd visor band, condensation sensor",
+ color: "Green Tint",
+ requiresRecalibration: false,
+ requiresCapabilityQuestions: false,
+ recalibrationType: null,
+ childParts: null,
+ childPartQuestions: [
+ {
+ questionSequence: 1,
+ questionText:
+ "Does the rubber seal around your windshield have a chrome strip running through it?",
+ answers: [
+ {
+ answerResult: "WKT D1106 C",
+ answerText: "Yes",
+ nextQuestionSequence: null,
+ },
+ {
+ answerResult: "WKT D1106 B",
+ answerText: "No",
+ nextQuestionSequence: null,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ partQuestions: null,
+ },
+ ],
+ });
+ useMainStore().lineItems = { glassParts: {} };
+
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ },
+ });
+
+ wrapper.setData({
+ selectedGlassParts: { "Rear-Stationary": { partNumber: "FW03861GTYN" } },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ await wrapper.vm.forwardButtonAction();
+
+ //Assert
+ expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
+ });
+
+ test("ForwardButtonAction triggers a router.navigate change if there are capability questions", async () => {
+ //Arrange
+ useMainStore().pageData = jest.fn();
+ useMainStore().pageData.mockReturnValue({
+ partsOrQuestions: [
+ {
+ glassName: "Single",
+ glassLocation: "Windshield",
+ parts: [
+ {
+ partNumber: "DB12209GTYN",
+ description: "heated glass, solar, 1 hole",
+ color: "Green Tint",
+ requiresRecalibration: false,
+ requiresCapabilityQuestions: true,
+ childParts: null,
+ },
+ ],
+ partQuestions: null,
+ },
+ ],
+ });
+ useMainStore().lineItems = { glassParts: {} };
+ useMainStore().vehicle = { carId: "TEST_CAR_ID" };
+
+ const { wrapper } = setupMocks({
+ mountOptionsMockData: {
+ router: {
+ navigate: jest.fn(),
+ },
+ route: {
+ query: {
+ issPage: "vehicle-parts",
+ },
+ },
+ actionList: [
+ {
+ actionName: "getCapabilityQuestions",
+ data: [],
+ },
+ ],
+ },
+ });
+
+ wrapper.setData({
+ selectedGlassParts: { "Rear-Stationary": { partNumber: "DB12209GTYN" } },
+ });
+
+ //Act
+ vehicleParts.beforeRouteEnter.call(
+ wrapper.vm,
+ { query: { issPage: "vehicle-parts" } },
+ undefined,
+ (c) => c(wrapper.vm)
+ );
+
+ await wrapper.vm.forwardButtonAction();
+
+ //Assert
+ expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
+ });
+
+});
+
+function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {} }) {
+ //Mock api responses
+ const apiResponses = {
+ cmsContent: {
+ SiteSubHeaderWidget: pageHeaderWidgetHeaderText,
+ VehicleBannerWidget: {
+ GenericVehicleImage:
+ "https://digitalisscms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
+ },
+ FunnelHeaderWidget: {
+ LogoImage:
+ "https://digitalisscms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
+ },
+ ColorQuestionWidget: "Please choose your rear window tint color",
+ FeatureQuestionWidget: "Ok no choose features",
+ AlertWidget: {
+ BodyText: "Please choose your tint color",
+ HeadlineText: "Just a few more steps to go",
+ },
+ },
+ };
+
+ const apiPromise = Promise.resolve(apiResponses);
+
+ settleAllPromises.mockImplementation(() => apiPromise);
+ fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
+
+ const mountOptions = getMountOptions(mountOptionsMockData);
+ const wrapper = shallowMount(vehicleParts, mountOptions);
+
+ const partQuestionRearWrapper = wrapper.findComponent({ name: "glassPartQuestion" });
+ partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent;
+
+ wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
+ wrapper.vm.$refs.siteFooter.removeLoader = jest.fn();
+
+ return { wrapper, apiPromise };
+}
diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue
new file mode 100644
index 00000000..b734f58a
--- /dev/null
+++ b/src/layouts/vehicle-parts/vehicle-parts.vue
@@ -0,0 +1,222 @@
+
+
+
+
+
diff --git a/src/mixins/vehicle-questions-mixin.js b/src/mixins/vehicle-questions-mixin.js
index d06c1010..533a2b27 100644
--- a/src/mixins/vehicle-questions-mixin.js
+++ b/src/mixins/vehicle-questions-mixin.js
@@ -1,6 +1,6 @@
import { issPageValues } from "@/router/router-constants/issPage-values";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
-import { useMainStore } from "../store";
+import { useMainStore } from "@/store";
export default {
methods: {
@@ -405,9 +405,7 @@ export default {
// mimic part-questions page data for consistency
for (let partOrQuestion of partsOrQuestions) {
if (this.hasCapabilityQuestions([partOrQuestion])) {
- let capabilityQuestionsForGlassLocation = (
- await self.mainStore.getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber)
- ).data;
+ let capabilityQuestionsForGlassLocation = (await useMainStore().getCapabilityQuestions(useMainStore().vehicle.carId, partOrQuestion.parts[0].partNumber));
capabilityQuestionsForGlassLocation.forEach((question) => {
question.answers = question.answers.map((answer) => {
diff --git a/src/store/index.js b/src/store/index.js
index 6a098a76..6a51b97e 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -91,6 +91,7 @@ export const useMainStore = defineStore({
getters: {
vehicle: (state) => state.order.vehicle,
damage: (state) => state.order.damage,
+ lineItems: (state) => state.order.lineItems,
eventBusItem: (state) => ( eventCategory, eventSubCategory) => {
const matchedEvent = state.applicationUser.eventBus.find(
@@ -354,12 +355,38 @@ export const useMainStore = defineStore({
this.applicationUser.pageData[issPageValues.CAPABILITY_QUESTIONS] = null;
},
+ resetSupportingItemsState() {
+ this.order.lineItems.supportingItems = null;
+ },
+
resetDamageState() {
this.order.damage.isRepair = null;
this.order.damage.numberOfChips = null;
this.order.damage.glassToReplace = null;
},
+ resetMoldingAndCapabilityQuestionAnswersIfNeeded(matchedParts) {
+ const partsOrQuestionsDataToCompareWith =
+ this.pageData(issPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ??
+ this.pageData(issPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ??
+ [];
+
+ const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith);
+ const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts);
+
+ const haveSelectedVehiclePartsChanged =
+ previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
+
+ if (haveSelectedVehiclePartsChanged) {
+ this.updateGlassParts(null);
+ this.resetSupportingItemsState();
+ this.updateMoldingQuestionAnswers(null);
+ this.updateCapabilityQuestionAnswers(null);
+ this.updatePageData({ page: issPageValues.MOLDING_QUESTIONS, data: null});
+ this.updatePageData({ page: issPageValues.CAPABILITY_QUESTIONS, data: null});
+ }
+ },
+
updateVehicleYear(year) {
if(this.order.vehicle.year != year)
{
@@ -726,3 +753,14 @@ function convertGlassPieceNamingFromApi(glassArray) {
return glassArray;
}
+function getAllPartNumbers(partsOrQuestions) {
+ return partsOrQuestions[0]?.parts
+ ? [...partsOrQuestions]
+ .map((glass) => glass.parts)
+ .flat()
+ .map((part) => part.partNumber)
+ .filter((partNumber) => !partNumber.toUpperCase().includes("FEE"))
+ .sort()
+ .join(",")
+ : [];
+}