diff --git a/jest.config.js b/jest.config.js index ec51cb6f7..5117c95e0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,7 +15,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 60, + statements: 70, }, }, }; diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js new file mode 100644 index 000000000..e5826c81d --- /dev/null +++ b/src/helpers/cms-content-helper.js @@ -0,0 +1,29 @@ +import { storeActions } from "@/constants/store-actions.js"; +import { widgetNames } from "@/constants/widget-names.js"; +import store from "@/store"; + +export function fetchCmsContentForPage(fmgPage) { + return store.dispatch(storeActions.GET_PAGE_DATA, { pageName: fmgPage }).then((response) => { + const pageDataFromCms = { + isCmsContentReady: false + }; + + response.data.Result.forEach((widget) => { + if (Object.values(widgetNames).includes(widget.Type)) { + // If we already have this widget, push it on the collection + if (widget.Type in pageDataFromCms) { + pageDataFromCms[widget.Type].push(widget.Model); + return; + } + + pageDataFromCms[widget.Type] = [widget.Model]; + } + }); + + // Our 'Page' is ready because we have data now + pageDataFromCms.isCmsContentReady = true; + + return pageDataFromCms; + }); +} + diff --git a/src/helpers/cms-helper.spec.js b/src/helpers/cms-helper.spec.js new file mode 100644 index 000000000..bb0edafcb --- /dev/null +++ b/src/helpers/cms-helper.spec.js @@ -0,0 +1,53 @@ +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import { dispatch } from "@/store"; + +jest.mock("@/store", () => ({ + dispatch: jest.fn() +})); + +it("cms-content-helper: Should return data from CMS", () => { + + // Arrange + const cmsMockData = { + Result: [ + { + Type: "VehicleBannerWidget", + Model: { + ImageId: "28452dcb-7762-4cc9-ab09-7643d0b89203", + GenericVehicleImage: "https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3", + GenericVehicleImageFilePath: "images/default-source/default-album/blurred-image.jpg" + } + }, + { + Type: "PageHeaderWidget", + Model: { + "HeaderText": "Select a year to get started" + } + }, + { + Type: "PageHeaderWidget", + Model: { + "HeaderText": "Select a model" + } + }, + { + Type: "RadioQuestionWidget", + Model: { + "QuestionText": "What year is your vehicle?" + } + } + ] + } + + dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData })); + + // Act + fetchCmsContentForPage('testPage').then((response) => { + + // Assert + expect(response.isCmsContentReady).toBe(true); + expect(response.PageHeaderWidget[0].HeaderText).toEqual('Select a year to get started'); + }); + +}); + diff --git a/src/helpers/layout-helper.js b/src/helpers/layout-helper.js new file mode 100644 index 000000000..d62d4d59f --- /dev/null +++ b/src/helpers/layout-helper.js @@ -0,0 +1,29 @@ +export function settleAllPromises(promiseResultMap) { + + // Pull our keys out of the promise 'table' + const promiseNames = Object.entries(promiseResultMap); + + return Promise.allSettled(promiseNames.map(e => e[1]).map(n => n.promise)) + .then(results => { + + const resultMap = {}; + + // Build a map of the results + for (let i = 0; i < results.length; ++i) { + + const promiseName = promiseNames[i][1].resultKey; + + // Some Promises like the cms content call don't have a 'data' field + // when returned, so other promises do. Map the results to the object + // so that the object is the return data. + + if (results[i].value.data === undefined) { + resultMap[promiseName] = results[i].value + } else { + resultMap[promiseName] = results[i].value.data; + } + } + + return resultMap; + }); +} \ No newline at end of file diff --git a/src/helpers/layout-helper.spec.js b/src/helpers/layout-helper.spec.js new file mode 100644 index 000000000..f85667f64 --- /dev/null +++ b/src/helpers/layout-helper.spec.js @@ -0,0 +1,28 @@ +import { settleAllPromises } from "@/helpers/layout-helper"; + +it("layout-helper: Should settle all promises and return mapped promise results", () => { + + // Arrange + const mockPromiseOne = Promise.resolve({ data: "test-data" }); + const mockPromiseTwo = Promise.resolve({ data: "test-data-two" }); + + const promiseResultMap = [ + { + resultKey: "MockResultOne", + promise: mockPromiseOne, + }, + { + resultKey: "MockResultTwo", + promise: mockPromiseTwo, + }, + ]; + + // Act + settleAllPromises(promiseResultMap).then(results => { + + // Assert + expect(results.MockResultOne).toEqual('test-data'); + expect(results.MockResultTwo).toEqual('test-data-two'); + }); + +}) \ No newline at end of file diff --git a/src/helpers/unit-test-helper.js b/src/helpers/unit-test-helper.js index 4919ed730..4210b56bc 100644 --- a/src/helpers/unit-test-helper.js +++ b/src/helpers/unit-test-helper.js @@ -1,7 +1,7 @@ import { storeActions } from "@/constants/store-actions"; import store from "@/store"; -export function getMountOptions(mockData, cmsMockData = null) { +export function getMountOptions(mockData) { // Define our mocks to attached to the 'global' object for Vue/Jest. const mocks = {}; @@ -18,16 +18,9 @@ export function getMountOptions(mockData, cmsMockData = null) { } }); - if(cmsMockData){ - mocks.GetContentFromCms = jest.fn(); - mocks.GetContentFromCms.mockImplementation(() => - { - return cmsMockData; - }); - } - // Mock store actions from js file mocks.storeActions = storeActions; + const global = { mocks: mocks, plugins: [store] diff --git a/src/layouts/vehicle-year/vehicle-year.spec.js b/src/layouts/vehicle-year/vehicle-year.spec.js index fbc3afedf..d3fd4fe77 100644 --- a/src/layouts/vehicle-year/vehicle-year.spec.js +++ b/src/layouts/vehicle-year/vehicle-year.spec.js @@ -1,27 +1,56 @@ -import { shallowMount, flushPromises } from "@vue/test-utils"; +import { shallowMount, flushPromises } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue"; +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-year.vue", () => { test("vehicle-year.vue should render data from CMS", async () => { // Arrange - const cmsMockData = { - PageHeaderWidget: [{ HeaderText: "Select a year to get started" }], - RadioQuestionWidget: [{ QuestionText: "What year is your vehicle?" }], + + // Our mock data for our call to settleAllPromises + const mockData = { + getPageContent: { + PageHeaderWidget: [{ HeaderText: "Select a year to get started" }], + RadioQuestionWidget: [{ QuestionText: "What year is your vehicle?" }], + isCmsContentReady: true, + }, + getVehicleYear: [2023, 2022, 2021] } - const mountOptions = getMountOptions({}, cmsMockData); + // our router information needed. + const to = { + query: { + fmgPage: 'vehicle-year' + } + }; + + const mountOptions = getMountOptions(mockData); + + // our mock implementation of settleAllPromises + settleAllPromises.mockImplementation(() => { return Promise.resolve(mockData);}); // Act const wrapper = shallowMount(vehicleYear, mountOptions); - await flushPromises(); + + // Call our beforeRouteEnter on the component. + // This passes (c) => c(wrapper.vm) so that next can be called and our + // data can be set. + vehicleYear.beforeRouteEnter.call(wrapper.vm, to, undefined, (c) => c(wrapper.vm)); + + await nextTick(); // Wait for the DOM to update. // Assert const header = await wrapper.find(".Header"); expect(header.attributes("text")).toEqual("Select a year to get started"); - const yearQuestion = await wrapper.findComponent({name: 'year-question'}); + const yearQuestion = wrapper.findComponent({ name: 'year-question' }); expect(yearQuestion.attributes("questiontext")).toEqual("What year is your vehicle?"); }); }); diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index 444619ab1..941d1328c 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -1,37 +1,68 @@ diff --git a/src/layouts/vehicle-year/year-question/year-question.spec.js b/src/layouts/vehicle-year/year-question/year-question.spec.js index a5ab50320..3d0843e10 100644 --- a/src/layouts/vehicle-year/year-question/year-question.spec.js +++ b/src/layouts/vehicle-year/year-question/year-question.spec.js @@ -1,28 +1 @@ -import { shallowMount, flushPromises } from "@vue/test-utils"; -import { getMountOptions } from "@/helpers/unit-test-helper.js"; -import { storeActions } from "@/constants/store-actions.js"; -import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; - -describe('year-question.vue', () => { - - test('year-question should call API to get years', async () => { - // Arrange - const mockDataAndAction = { - actionList: [{ actionName: storeActions.GET_VEHICLE_YEARS, data: ["2023", "2022", "2021"], - }, - ], - }; - - const mountOptions = getMountOptions(mockDataAndAction); - - // Act - const wrapper = shallowMount(yearQuestion, mountOptions); - await wrapper.setProps({questionText: 'What year is your vehicle?'}) - await flushPromises(); - - // Assert - const radioQuestion = await wrapper.findComponent({name: 'radioQuestion'}); - expect(radioQuestion.attributes('questiontext')).toBe("What year is your vehicle?"); - expect(radioQuestion.attributes('answers')).toBe('2023,2022,2021'); - }); -}); +test.todo("some test to be written in the future"); diff --git a/src/layouts/vehicle-year/year-question/year-question.vue b/src/layouts/vehicle-year/year-question/year-question.vue index 4aedb12df..e3b2fc4cb 100644 --- a/src/layouts/vehicle-year/year-question/year-question.vue +++ b/src/layouts/vehicle-year/year-question/year-question.vue @@ -9,16 +9,9 @@ export default { name: "year-question", props: { questionText: String, - }, - data() { - return { - years: [], - }; + years: Array }, created() { - this.dispatchNonBlockingStoreAction(this.storeActions.GET_VEHICLE_YEARS, {}).then((response) => { - this.years = response.data; - }); }, components: { radioQuestion, diff --git a/src/mixins/base-mixin.js b/src/mixins/base-mixin.js index db2e75618..979b03139 100644 --- a/src/mixins/base-mixin.js +++ b/src/mixins/base-mixin.js @@ -23,30 +23,6 @@ export default { return this.$store.dispatch(type, payload); }, - GetContentFromCms() { - return this.dispatchNonBlockingStoreAction(this.storeActions.GET_PAGE_DATA,{ pageName: this.$route.query.fmgPage }).then((response) => { - - const pageDataFromCms = {}; - - response.data.Result.forEach((widget) => { - if (Object.values(this.widgetNames).includes(widget.Type)) { - // If we already have this widget, push it on the collection - if (widget.Type in pageDataFromCms) { - pageDataFromCms[widget.Type].push(widget.Model); - return; - } - - pageDataFromCms[widget.Type] = [widget.Model]; - - } - }); - - // Our 'Page' is ready because we have data now - this.isCmsContentReady = true; - - return pageDataFromCms; - }); - }, }, computed: { storeActions() { diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index a6eb8d504..e3acbcbb7 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -1,7 +1,6 @@ import baseMixin from "@/mixins/base-mixin" import { storeActions } from "@/constants/store-actions.js"; import { widgetNames } from "@/constants/widget-names.js"; -import { flushPromises } from "@vue/test-utils"; describe("baseMixin.js", () => { test('dispatchNonblockingStoreAction: calls dispatch with type and payload', () => { @@ -23,60 +22,6 @@ describe("baseMixin.js", () => { expect(mixIn.methods.$store.dispatch).toBeCalledWith(type, payload); }); - - test('GetContentFromCms: Should return mapped data and call dispatchNonblockingStoreAction', async () => { - const mixIn = getMixInInstance({}); - - // Mock dispatch action - mixIn.methods.dispatchNonBlockingStoreAction = jest.fn(); - mixIn.methods.dispatchNonBlockingStoreAction.mockImplementation((action) => { - if (action === 'getPageData') { - return Promise.resolve({ - data: { - Result: [ - { - Type: "PageConfigWidget", - Model: { - LayoutNames: [ - "None", - "vehicle-year" - ], - LayoutStyle: "vehicle-year" - } - }, - { - Type: "RadioQuestionWidget", - Model: { - "QuestionText": "What year is your vehicle?" - } - }, - { - Type: "RadioQuestionWidget", - Model: { - "QuestionText": "This is a second radio question" - } - }, - { - Type: "PageHeaderWidget", - Model: { - "HeaderText": "Select a year to get started" - } - } - ] - }, - }); - } - }); - - const result = await mixIn.methods.GetContentFromCms(); - await flushPromises(); - - - expect(mixIn.methods.dispatchNonBlockingStoreAction).toBeCalledWith(storeActions.GET_PAGE_DATA, { pageName: 'test-page'}); - - expect(result.PageHeaderWidget[0].HeaderText).toEqual('Select a year to get started'); - - }); }) function getMixInInstance({ isDispatchSuccess = true }) {