From d3d880b2b3ac5fb6af41c5388c2c88bc9b477ba6 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 29 Nov 2021 16:14:32 -0500 Subject: [PATCH 1/5] Added Promise.All --- .../{cms-adapter.js => cms-content-helper.js} | 3 +- src/layouts/vehicle-year/vehicle-year.vue | 62 ++++++++++--------- .../year-question/year-question.vue | 3 - 3 files changed, 35 insertions(+), 33 deletions(-) rename src/helpers/{cms-adapter.js => cms-content-helper.js} (94%) diff --git a/src/helpers/cms-adapter.js b/src/helpers/cms-content-helper.js similarity index 94% rename from src/helpers/cms-adapter.js rename to src/helpers/cms-content-helper.js index 721569396..da9d3b29f 100644 --- a/src/helpers/cms-adapter.js +++ b/src/helpers/cms-content-helper.js @@ -2,7 +2,7 @@ import { storeActions } from "@/constants/store-actions.js"; import { widgetNames } from "@/constants/widget-names.js"; import store from "@/store"; -export function GetContentFromCms(fmgPage) { +export function fetchCmsContentForPage(fmgPage) { return store.dispatch(storeActions.GET_PAGE_DATA, { pageName: fmgPage }).then((response) => { const pageDataFromCms = { @@ -18,7 +18,6 @@ export function GetContentFromCms(fmgPage) { } pageDataFromCms[widget.Type] = [widget.Model]; - } }); diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index dc610dac3..14d1e85cb 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -1,40 +1,46 @@ diff --git a/src/layouts/vehicle-year/year-question/year-question.vue b/src/layouts/vehicle-year/year-question/year-question.vue index 4aedb12df..5df3cffa8 100644 --- a/src/layouts/vehicle-year/year-question/year-question.vue +++ b/src/layouts/vehicle-year/year-question/year-question.vue @@ -16,9 +16,6 @@ export default { }; }, created() { - this.dispatchNonBlockingStoreAction(this.storeActions.GET_VEHICLE_YEARS, {}).then((response) => { - this.years = response.data; - }); }, components: { radioQuestion, From da9aa475218fe35e22e654ede0ac5f35c5417e93 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 30 Nov 2021 14:30:35 -0500 Subject: [PATCH 2/5] Created layout-helper --- src/helpers/layout-helper.js | 29 ++++++++++ src/helpers/unit-test-helper.js | 14 ++--- src/layouts/vehicle-year/vehicle-year.spec.js | 43 ++++++++++++--- src/layouts/vehicle-year/vehicle-year.vue | 27 +++++++--- .../year-question/year-question.spec.js | 29 +--------- .../year-question/year-question.vue | 6 +-- src/mixins/base-mixin.spec.js | 54 ------------------- 7 files changed, 89 insertions(+), 113 deletions(-) create mode 100644 src/helpers/layout-helper.js diff --git a/src/helpers/layout-helper.js b/src/helpers/layout-helper.js new file mode 100644 index 000000000..f3b4284dd --- /dev/null +++ b/src/helpers/layout-helper.js @@ -0,0 +1,29 @@ +export function settleAllPromises(layoutPromiseTable) { + + // Pull our keys out of the promise 'table' + const promiseNames = Object.entries(layoutPromiseTable); + + 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].key; + + // 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/unit-test-helper.js b/src/helpers/unit-test-helper.js index 94027cc05..9b5f2a763 100644 --- a/src/helpers/unit-test-helper.js +++ b/src/helpers/unit-test-helper.js @@ -1,6 +1,6 @@ import { storeActions } from "@/constants/store-actions"; -export function getMountOptions(mockData, cmsMockData = null) { +export function getMountOptions(mockData) { // Define our mocks to attached to the 'global' object for Vue/Jest. const mocks = {}; @@ -17,20 +17,12 @@ 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, }; return { global }; -} - +} \ No newline at end of file diff --git a/src/layouts/vehicle-year/vehicle-year.spec.js b/src/layouts/vehicle-year/vehicle-year.spec.js index fbc3afedf..1d5096e60 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 = { + pageContentPromise: { + PageHeaderWidget: [{ HeaderText: "Select a year to get started" }], + RadioQuestionWidget: [{ QuestionText: "What year is your vehicle?" }], + isCmsContentReady: true, + }, + vehicleYearsPromise: [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 14d1e85cb..86c89c9f8 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -2,7 +2,7 @@
- +
@@ -11,6 +11,7 @@ import yearQuestion from "@/layouts/vehicle-year/year-question/year-question"; import pageHeader from "@/ux-components/header/header"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import { settleAllPromises } from "@/helpers/layout-helper"; import store from "@/store"; import { storeActions } from "@/constants/store-actions.js"; @@ -20,23 +21,33 @@ export default { return { pageHeaderWidgets: {}, radioQuestionWidgets: {}, + vehicleYears: [], }; }, computed: {}, beforeRouteEnter(to, from, next) { - Promise.allSettled([ - fetchCmsContentForPage(to.query.fmgPage), - store.dispatch(storeActions.GET_VEHICLE_YEARS, {}), - ]).then(([contentPromise, getYearsPromise]) => { - + const layoutPromiseTable = [ + { + key: "pageContentPromise", + promise: fetchCmsContentForPage(to.query.fmgPage), + }, + { + key: "vehicleYearsPromise", + promise: store.dispatch(storeActions.GET_VEHICLE_YEARS, {}), + }, + ]; + settleAllPromises(layoutPromiseTable).then((resultMap) => { // Call our next function to transition to the next page. next((vm) => { - vm.pageHeaderWidgets = contentPromise.value.PageHeaderWidget[0]; - vm.radioQuestionWidgets = contentPromise.value.RadioQuestionWidget[0]; + vm.pageHeaderWidgets = resultMap.pageContentPromise.PageHeaderWidget[0]; + vm.radioQuestionWidgets = resultMap.pageContentPromise.RadioQuestionWidget[0]; + vm.vehicleYears = resultMap.vehicleYearsPromise; + }); }); + }, components: { yearQuestion, 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 5df3cffa8..e3b2fc4cb 100644 --- a/src/layouts/vehicle-year/year-question/year-question.vue +++ b/src/layouts/vehicle-year/year-question/year-question.vue @@ -9,11 +9,7 @@ export default { name: "year-question", props: { questionText: String, - }, - data() { - return { - years: [], - }; + years: Array }, created() { }, diff --git a/src/mixins/base-mixin.spec.js b/src/mixins/base-mixin.spec.js index a6eb8d504..628a3ef2f 100644 --- a/src/mixins/base-mixin.spec.js +++ b/src/mixins/base-mixin.spec.js @@ -23,60 +23,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 }) { From becd4b9e93095bd8ccd6914a4adbbdd1e8d67182 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 30 Nov 2021 15:11:57 -0500 Subject: [PATCH 3/5] readability changes --- src/layouts/vehicle-year/vehicle-year.spec.js | 4 ++-- src/layouts/vehicle-year/vehicle-year.vue | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/layouts/vehicle-year/vehicle-year.spec.js b/src/layouts/vehicle-year/vehicle-year.spec.js index 1d5096e60..d3fd4fe77 100644 --- a/src/layouts/vehicle-year/vehicle-year.spec.js +++ b/src/layouts/vehicle-year/vehicle-year.spec.js @@ -16,12 +16,12 @@ describe("vehicle-year.vue", () => { // Our mock data for our call to settleAllPromises const mockData = { - pageContentPromise: { + getPageContent: { PageHeaderWidget: [{ HeaderText: "Select a year to get started" }], RadioQuestionWidget: [{ QuestionText: "What year is your vehicle?" }], isCmsContentReady: true, }, - vehicleYearsPromise: [2023, 2022, 2021] + getVehicleYear: [2023, 2022, 2021] } // our router information needed. diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index ec6838b10..688b400bb 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -27,25 +27,29 @@ export default { computed: {}, beforeRouteEnter(to, from, next) { - const layoutPromiseTable = [ + const contentPromise = fetchCmsContentForPage(to.query.fmgPage); + const getVehicleYearPromise = store.dispatch(storeActions.GET_VEHICLE_YEARS, {}); + + const promiseResultMap = [ { - key: "pageContentPromise", - promise: fetchCmsContentForPage(to.query.fmgPage), + resultKey: "getPageContent", + promise: contentPromise, }, { - key: "vehicleYearsPromise", - promise: store.dispatch(storeActions.GET_VEHICLE_YEARS, {}), + resultKey: "getVehicleYear", + promise: getVehicleYearPromise, }, ]; - settleAllPromises(layoutPromiseTable).then((resultMap) => { + settleAllPromises(promiseResultMap).then((resultMap) => { + // Call our next function to transition to the next page. next((vm) => { - vm.pageHeaderWidgets = resultMap.pageContentPromise.PageHeaderWidget[0]; - vm.radioQuestionWidgets = resultMap.pageContentPromise.RadioQuestionWidget[0]; - vm.vehicleYears = resultMap.vehicleYearsPromise; - + vm.pageHeaderWidgets = resultMap.getPageContent.PageHeaderWidget[0]; + vm.radioQuestionWidgets = resultMap.getPageContent.RadioQuestionWidget[0]; + vm.vehicleYears = resultMap.getVehicleYear; }); + }); }, From 94bb61b6325fdbbc4e21f0f465dced7f2932b2d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 30 Nov 2021 16:51:26 -0500 Subject: [PATCH 4/5] Unit tests --- jest.config.js | 2 +- src/helpers/cms-content-helper.js | 1 - src/helpers/cms-helper.spec.js | 53 +++++++++++++++++++++++++++++++ src/helpers/layout-helper.js | 8 ++--- src/helpers/layout-helper.spec.js | 28 ++++++++++++++++ src/mixins/base-mixin.js | 24 -------------- src/mixins/base-mixin.spec.js | 1 - 7 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 src/helpers/cms-helper.spec.js create mode 100644 src/helpers/layout-helper.spec.js 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 index da9d3b29f..e5826c81d 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -4,7 +4,6 @@ import store from "@/store"; export function fetchCmsContentForPage(fmgPage) { return store.dispatch(storeActions.GET_PAGE_DATA, { pageName: fmgPage }).then((response) => { - const pageDataFromCms = { isCmsContentReady: false }; 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 index f3b4284dd..d62d4d59f 100644 --- a/src/helpers/layout-helper.js +++ b/src/helpers/layout-helper.js @@ -1,7 +1,7 @@ -export function settleAllPromises(layoutPromiseTable) { +export function settleAllPromises(promiseResultMap) { // Pull our keys out of the promise 'table' - const promiseNames = Object.entries(layoutPromiseTable); + const promiseNames = Object.entries(promiseResultMap); return Promise.allSettled(promiseNames.map(e => e[1]).map(n => n.promise)) .then(results => { @@ -11,8 +11,8 @@ export function settleAllPromises(layoutPromiseTable) { // Build a map of the results for (let i = 0; i < results.length; ++i) { - const promiseName = promiseNames[i][1].key; - + 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. 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/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 628a3ef2f..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', () => { From 0f763bbea1cf0fcbfc047718396a880c5a23a1ba Mon Sep 17 00:00:00 2001 From: FrankSafelite Date: Wed, 1 Dec 2021 09:10:37 -0500 Subject: [PATCH 5/5] Re-added vehicle banner --- src/layouts/vehicle-year/vehicle-year.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/layouts/vehicle-year/vehicle-year.vue b/src/layouts/vehicle-year/vehicle-year.vue index 688b400bb..941d1328c 100644 --- a/src/layouts/vehicle-year/vehicle-year.vue +++ b/src/layouts/vehicle-year/vehicle-year.vue @@ -1,6 +1,7 @@ -