diff --git a/jest.config.js b/jest.config.js index e36f1a70d..720237d10 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,7 +16,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 70, + statements: 85, }, }, }; diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue index 97ebf1310..d86dcd2ce 100644 --- a/src/common-components/button-question/button-question.vue +++ b/src/common-components/button-question/button-question.vue @@ -10,14 +10,15 @@ {{groupName}} @@ -46,3 +47,15 @@ export default { }, }; + + \ No newline at end of file diff --git a/src/common-components/site-header/site-header.spec.js b/src/common-components/funnel-header/funnel-header.spec.js similarity index 68% rename from src/common-components/site-header/site-header.spec.js rename to src/common-components/funnel-header/funnel-header.spec.js index 2a471fe26..cae21a179 100644 --- a/src/common-components/site-header/site-header.spec.js +++ b/src/common-components/funnel-header/funnel-header.spec.js @@ -1,12 +1,12 @@ import { shallowMount } from "@vue/test-utils"; -import siteHeader from "./site-header"; +import funnelHeader from "./funnel-header"; -describe("siteHeader", () => { +describe("funnelHeader", () => { test("renders the logo image", () => { // Arrange // Act - const wrapper = shallowMount(siteHeader, { + const wrapper = shallowMount(funnelHeader, { propsData: { imageSrc: "image_url", }, diff --git a/src/common-components/site-header/site-header.vue b/src/common-components/funnel-header/funnel-header.vue similarity index 90% rename from src/common-components/site-header/site-header.vue rename to src/common-components/funnel-header/funnel-header.vue index 441b188c4..7b6c6efc6 100644 --- a/src/common-components/site-header/site-header.vue +++ b/src/common-components/funnel-header/funnel-header.vue @@ -9,11 +9,12 @@ + + diff --git a/src/common-components/vehicle-banner/vehicle-banner.vue b/src/common-components/vehicle-banner/vehicle-banner.vue index 51e8d8aa3..984657a47 100644 --- a/src/common-components/vehicle-banner/vehicle-banner.vue +++ b/src/common-components/vehicle-banner/vehicle-banner.vue @@ -1,5 +1,5 @@ + + diff --git a/src/layouts/vehicle-model/model-question/model-question.spec.js b/src/layouts/vehicle-model/model-question/model-question.spec.js new file mode 100644 index 000000000..d6259eb72 --- /dev/null +++ b/src/layouts/vehicle-model/model-question/model-question.spec.js @@ -0,0 +1,82 @@ +import modelQuestion from "@/layouts/vehicle-model/model-question/model-question"; +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; +jest.mock("@/store", () => { return {}; }, {virtual: true}); + +describe("model-question.vue", () => { + test("Selected model is emitted upon selection.", async () => { + + //Arrange + const { wrapper } = setupMocks({ modelValueProp: "Accord" }); + const modelToSelect = "Civic"; + + //Act + wrapper.setData({ selectedModel: modelToSelect }); + await wrapper.vm.$nextTick(); + + //Assert + expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["Civic"]); + }); +}); + +describe("model-question.vue", () => { + test("CMS question text is used as radio question text.", async () => { + + //Arrange + const { wrapper, cmsContent } = setupMocks({ cmsQuestionText: "What model is your vehicle?" }); + + //Act + modelQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, null); + + //Assert + const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" }); + expect(buttonQuestionComponent.attributes("questiontext")).toBe("What model is your vehicle?"); + }); +}); + +describe("model-question.vue", () => { + test("Data from store api are used as radio question answers.", async () => { + + //Arrange + const { wrapper, cmsContent } = setupMocks({ dataFromStoreApi: ["accord", "civic", "insight"] }); + + //Act + const initialData = modelQuestion.methods.loadInitialData.call(wrapper.vm); + modelQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, initialData); + + //Assert + const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" }); + expect(buttonQuestionComponent.attributes("answers")).toBe("accord,civic,insight"); + }); +}); + + +function setupMocks({ + modelValueProp = "1900", + cmsQuestionText = "CMS text goes here", + dataFromStoreApi = [], +}) { + + //Mock store + store.dispatch = jest.fn(() => dataFromStoreApi); + store.getters = { vehicle: {year: 2019, make: 'honda'} }; + const mountOptions = getMountOptions({ + store: { + dispatch: store.dispatch, + getters: store.getters, + }, + }); + + //Mock props + mountOptions.propsData = { + modelValue: modelValueProp, + }; + const wrapper = shallowMount(modelQuestion, mountOptions); + + //Mock CMS content + const cmsContent = { + QuestionText: cmsQuestionText + }; + return { wrapper, cmsContent }; +} \ No newline at end of file diff --git a/src/layouts/vehicle-model/model-question/model-question.vue b/src/layouts/vehicle-model/model-question/model-question.vue new file mode 100644 index 000000000..95b72909b --- /dev/null +++ b/src/layouts/vehicle-model/model-question/model-question.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/layouts/vehicle-model/vehicle-model.spec.js b/src/layouts/vehicle-model/vehicle-model.spec.js new file mode 100644 index 000000000..8ee3885de --- /dev/null +++ b/src/layouts/vehicle-model/vehicle-model.spec.js @@ -0,0 +1,93 @@ +import { shallowMount, flushPromises } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import vehicleModel from "@/layouts/vehicle-model/vehicle-model.vue"; +import modelQuestion from "@/layouts/vehicle-model/model-question/model-question"; +import { settleAllPromises } from "@/helpers/layout-helper.js"; +import { nextTick } from "vue"; + +// Mock our module for promises. +jest.mock("@/helpers/layout-helper.js", () => ({ + settleAllPromises: jest.fn(), +})); + +describe("vehicle-model.vue", () => { + test("Model question component is initized with api data", async (done) => { + + //Arange + const radioQuestionCmsContent = { QuestionText: "What model is your vehicle?" }; + const modelQuestionInitialData = ["accord", "civic", "insight"]; + const { wrapper, apiPromise } = setupMocks( { + radioQuestionCmsContent: radioQuestionCmsContent, + modelQuestionInitialData: modelQuestionInitialData, + } ); + + //Act + vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm)); + + //Assert + apiPromise.finally(() => { + expect(modelQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, modelQuestionInitialData); + done(); + }); + }); +}); + +describe("vehicle-model.vue", () => { + test("Page header is passed data from CMS", async (done) => { + + //Arange + const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a model to get started" }); + + //Act + vehicleModel.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-model" } }, undefined, (c) => c(wrapper.vm)); + + //Assert + const header = await wrapper.find(".Header"); + apiPromise.finally(() => { + expect(header.attributes("text")).toEqual("Select a model to get started"); + done(); + }); + }); +}); + +function setupMocks({ + radioQuestionCmsContent = {}, + modelQuestionInitialData = {}, + pageHeaderWidgetHeaderText = {}, +}) { + + //Mock api responses + const apiResponses = { + cmsContent: { + PageHeaderWidget: [{ HeaderText: pageHeaderWidgetHeaderText }], + RadioQuestionWidget: [radioQuestionCmsContent], + VehicleBannerWidget: [ + { + GenericVehicleImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", + }, + ], + SiteHeaderWidget: [ + { + LogoImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", + }, + ], + }, + modelQuestionInitialData: modelQuestionInitialData, + }; + const apiPromise = Promise.resolve(apiResponses); + settleAllPromises.mockImplementation(() => apiPromise); + + //Mock model question methods + modelQuestion.methods = { + loadInitialData: jest.fn(), + initializeComponent: jest.fn(), + }; + const mountOptions = getMountOptions({ }); + const wrapper = shallowMount(vehicleModel, mountOptions); + const modelQuestionWrapper = wrapper.findComponent({ name: "modelQuestion" }); + modelQuestionWrapper.vm.initializeComponent = modelQuestion.methods.initializeComponent; + + return { wrapper, apiPromise }; +} \ No newline at end of file diff --git a/src/layouts/vehicle-model/vehicle-model.vue b/src/layouts/vehicle-model/vehicle-model.vue new file mode 100644 index 000000000..1d86c99b4 --- /dev/null +++ b/src/layouts/vehicle-model/vehicle-model.vue @@ -0,0 +1,90 @@ + + + diff --git a/src/layouts/vehicle-style/style-question/style-question.spec.js b/src/layouts/vehicle-style/style-question/style-question.spec.js new file mode 100644 index 000000000..f03ce5e96 --- /dev/null +++ b/src/layouts/vehicle-style/style-question/style-question.spec.js @@ -0,0 +1,82 @@ +import styleQuestion from "@/layouts/vehicle-style/style-question/style-question"; +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; +jest.mock("@/store", () => { return {}; }, {virtual: true}); + +describe("style-question.vue", () => { + test("Selected style is emitted upon selection.", async () => { + + //Arrange + const { wrapper } = setupMocks({ modelValueProp: "2 Door" }); + const styleToSelect = "4 Door"; + + //Act + wrapper.setData({ selectedStyle: styleToSelect }); + await wrapper.vm.$nextTick(); + + //Assert + expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["4 Door"]); + }); +}); + +describe("style-question.vue", () => { + test("CMS question text is used as radio question text.", async () => { + + //Arrange + const { wrapper, cmsContent } = setupMocks({ cmsQuestionText: "What style is your vehicle?" }); + + //Act + styleQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, null); + + //Assert + const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" }); + expect(buttonQuestionComponent.attributes("questiontext")).toBe("What style is your vehicle?"); + }); +}); + +describe("style-question.vue", () => { + test("Data from store api are used as radio question answers.", async () => { + + //Arrange + const { wrapper, cmsContent } = setupMocks({ dataFromStoreApi: ["2 Door", "4 Door"] }); + + //Act + const initialData = styleQuestion.methods.loadInitialData.call(wrapper.vm); + styleQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, initialData); + + //Assert + const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" }); + expect(buttonQuestionComponent.attributes("answers")).toBe("2 Door,4 Door"); + }); +}); + + +function setupMocks({ + modelValueProp = "1900", + cmsQuestionText = "CMS text goes here", + dataFromStoreApi = [], +}) { + + //Mock store + store.dispatch = jest.fn(() => dataFromStoreApi); + store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc'} }; + const mountOptions = getMountOptions({ + store: { + dispatch: store.dispatch, + getters: store.getters, + }, + }); + + //Mock props + mountOptions.propsData = { + modelValue: modelValueProp, + }; + const wrapper = shallowMount(styleQuestion, mountOptions); + + //Mock CMS content + const cmsContent = { + QuestionText: cmsQuestionText + }; + return { wrapper, cmsContent }; +} \ No newline at end of file diff --git a/src/layouts/vehicle-style/style-question/style-question.vue b/src/layouts/vehicle-style/style-question/style-question.vue new file mode 100644 index 000000000..17a008473 --- /dev/null +++ b/src/layouts/vehicle-style/style-question/style-question.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/layouts/vehicle-style/vehicle-style.spec.js b/src/layouts/vehicle-style/vehicle-style.spec.js new file mode 100644 index 000000000..90dd6e926 --- /dev/null +++ b/src/layouts/vehicle-style/vehicle-style.spec.js @@ -0,0 +1,120 @@ +import { shallowMount, flushPromises } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import vehicleStyle from "@/layouts/vehicle-style/vehicle-style.vue"; +import styleQuestion from "@/layouts/vehicle-style/style-question/style-question"; +import { settleAllPromises } from "@/helpers/layout-helper.js"; +import { nextTick } from "vue"; + +// Mock our module for promises. +jest.mock("@/helpers/layout-helper.js", () => ({ + settleAllPromises: jest.fn(), +})); + +describe("vehicle-style.vue", () => { + test("Style question component is initized with api data", async (done) => { + + //Arrange + const radioQuestionCmsContent = { QuestionText: "What style is your vehicle?" }; + const styleQuestionInitialData = ["2 Door", "4 Door"]; + const { wrapper, apiPromise } = setupMocks( { + radioQuestionCmsContent: radioQuestionCmsContent, + styleQuestionInitialData: styleQuestionInitialData, + } ); + + //Act + vehicleStyle.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-style" } }, undefined, (c) => c(wrapper.vm)); + + //Assert + apiPromise.finally(() => { + expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, styleQuestionInitialData); + done(); + }); + }); +}); + +describe("vehicle-style.vue", () => { + test("Page header is passed data from CMS", async (done) => { + + //Arrange + const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a style to get started" }); + + //Act + vehicleStyle.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-style" } }, undefined, (c) => c(wrapper.vm)); + + //Assert + const header = await wrapper.find(".Header"); + apiPromise.finally(() => { + expect(header.attributes("text")).toEqual("Select a style to get started"); + done(); + }); + }); +}); + +describe("vehicle-style.vue", () => { + test("BackButtonAction triggers a router.navigate change", async (done) => { + + //Arrange + const { wrapper, apiPromise } = setupMocks( { + pageHeaderWidgetHeaderText: "Select a style to get started", + mountOptionsMockData: { + router: { + navigate: jest.fn() + }, + }, + }); + + //Act + vehicleStyle.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-style" } }, undefined, (c) => c(wrapper.vm)); + wrapper.vm.backButtonAction(); + await nextTick(); + + //Assert + apiPromise.finally(() => { + expect(wrapper.vm.$router.navigate).toHaveBeenCalled(); + done(); + }); + }); +}); + +function setupMocks({ + radioQuestionCmsContent = {}, + styleQuestionInitialData = {}, + pageHeaderWidgetHeaderText = {}, + mountOptionsMockData = {}, +}) { + + //Mock api responses + const apiResponses = { + cmsContent: { + PageHeaderWidget: [{ HeaderText: pageHeaderWidgetHeaderText }], + RadioQuestionWidget: [radioQuestionCmsContent], + VehicleBannerWidget: [ + { + GenericVehicleImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", + }, + ], + SiteHeaderWidget: [ + { + LogoImage: + "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3", + }, + ], + }, + styleQuestionInitialData: styleQuestionInitialData, + }; + const apiPromise = Promise.resolve(apiResponses); + settleAllPromises.mockImplementation(() => apiPromise); + + //Mock style question methods + styleQuestion.methods = { + loadInitialData: jest.fn(), + initializeComponent: jest.fn(), + }; + const mountOptions = getMountOptions(mountOptionsMockData); + const wrapper = shallowMount(vehicleStyle, mountOptions); + const styleQuestionWrapper = wrapper.findComponent({ name: "styleQuestion" }); + styleQuestionWrapper.vm.initializeComponent = styleQuestion.methods.initializeComponent; + + return { wrapper, apiPromise }; +} \ No newline at end of file diff --git a/src/layouts/vehicle-style/vehicle-style.vue b/src/layouts/vehicle-style/vehicle-style.vue new file mode 100644 index 000000000..6b5d4bf86 --- /dev/null +++ b/src/layouts/vehicle-style/vehicle-style.vue @@ -0,0 +1,90 @@ + + + diff --git a/src/layouts/vehicle-year/vehicle-year.spec.js b/src/layouts/vehicle-year/vehicle-year.spec.js index ae3d44a6a..594630ba4 100644 --- a/src/layouts/vehicle-year/vehicle-year.spec.js +++ b/src/layouts/vehicle-year/vehicle-year.spec.js @@ -13,7 +13,7 @@ jest.mock("@/helpers/layout-helper.js", () => ({ describe("vehicle-year.vue", () => { test("Year question component is initized with api data", async (done) => { - //Arange + //Arrange const radioQuestionCmsContent = { QuestionText: "What year is your vehicle?" }; const yearQuestionInitialData = ["2023", "2022", "2021"]; const { wrapper, apiPromise } = setupMocks( { @@ -35,7 +35,7 @@ describe("vehicle-year.vue", () => { describe("vehicle-year.vue", () => { test("Page header is passed data from CMS", async (done) => { - //Arange + //Arrange const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a year to get started" }); //Act @@ -54,6 +54,7 @@ function setupMocks({ radioQuestionCmsContent = {}, yearQuestionInitialData = {}, pageHeaderWidgetHeaderText = {}, + mountOptionsMockData = {}, }) { //Mock api responses @@ -84,7 +85,7 @@ function setupMocks({ loadInitialData: jest.fn(), initializeComponent: jest.fn(), }; - const mountOptions = getMountOptions({ }); + const mountOptions = getMountOptions(mountOptionsMockData); const wrapper = shallowMount(vehicleYear, mountOptions); const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" }); yearQuestionWrapper.vm.initializeComponent = yearQuestion.methods.initializeComponent; diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index 8dc9b72d9..48508adc2 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -1,10 +1,13 @@