diff --git a/jest.config.js b/jest.config.js index e36f1a70d..fd7713966 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: 80, }, }, }; diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index c12820fe0..1d901d27e 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -9,17 +9,84 @@ export function fetchCmsContentForPage(fmgPage) { const pageDataFromCms = {}; response.data.Result.forEach((widget) => { - if (Object.values(widgetNames).includes(widget.Type)) { + + let widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Type); + + if (Object.values(widgetNames).includes(widgetWithReplacements.Type)) { // If we already have this widget, push it on the collection - if (widget.Type in pageDataFromCms) { - pageDataFromCms[widget.Type].push(widget.Model); + if (widgetWithReplacements.Type in pageDataFromCms) { + pageDataFromCms[widgetWithReplacements.Type].push(widgetWithReplacements.Model); return; } - pageDataFromCms[widget.Type] = [widget.Model]; + pageDataFromCms[widgetWithReplacements.Type] = [widgetWithReplacements.Model]; } }); return pageDataFromCms; }); +} + +// Function to convert a string, into a matching global state item. +function mapStringToState(str) { + + // Pull the state string out of our dynamic string from the CMS + const regexExp = new RegExp('{(.*?):(.*?)}', 'g'); + const matches = [...str.matchAll(regexExp)]; + const stateString = matches.map(m => m[2]).toString(); + + // Convert the state string to a state object + let storeState = store.state; + + for (const s of stateString.split('.')) { + if (storeState[s] != undefined) { + storeState = storeState[s]; + } else { + return false; // If we can't find our state variable, break our loop. Don't break our code. + } + } + + return storeState; +} + +// Parent function for processWidgetItemForReplacement. This will loop through the parent +// object and pass any objects that need additional processing to the processWidgetItemForReplacement function. +function findAndReplaceGlobalStateValues(widgetModel, widgetType) { + + const objWithReplacements = { + Type: widgetType, + Model: {} + }; + + Object.keys(widgetModel).forEach(key => { + + let modelWithReplacements = processWidgetItemForReplacement(widgetModel, key); + + objWithReplacements.Model[key] = modelWithReplacements; + }); + + return objWithReplacements; +} + +// This function will process the widget item and replace any global state variables with their values. +// This is a recursive function, it will call itself until it runs out of items to iterate on given the object. +function processWidgetItemForReplacement(widgetModel, key) { + // If we have a string, and it needs to be replaced. + if (typeof widgetModel[key] === 'string') { + if (widgetModel[key].includes('{globalState:')) { + widgetModel[key] = mapStringToState(widgetModel[key]); + } + return widgetModel[key]; + } + + // If we have an object. array, etc + if (typeof widgetModel[key] === 'object' && Object.keys(widgetModel[key]).length) { + Object.keys(widgetModel[key]).forEach(item => { + processWidgetItemForReplacement(widgetModel[key], item); + }); + + return widgetModel[key]; + } + + return widgetModel[key]; } \ No newline at end of file diff --git a/src/helpers/cms-helper.spec.js b/src/helpers/cms-helper.spec.js index e9bd66653..0f03ab604 100644 --- a/src/helpers/cms-helper.spec.js +++ b/src/helpers/cms-helper.spec.js @@ -3,50 +3,138 @@ import { dispatch } from "@/store"; jest.mock("@/store", () => ({ dispatch: jest.fn(), + state: { + order: { vehicle: { year: "2019", make: "Acura" } } + } })); -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 })); +describe("cms-content-helper.js", () => { + it("Should return data from CMS", () => { + // Arrange + const cmsMockData = { + Result: [ + { + 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.PageHeaderWidget[0].HeaderText).toEqual( + "Select a year to get started" + ); + }); + }); - // Act - fetchCmsContentForPage("testPage").then((response) => { - // Assert - expect(response.PageHeaderWidget[0].HeaderText).toEqual( - "Select a year to get started" - ); +}); + +describe("cms-content-helper.js", () => { + it("Should replace strings for global state", () => { + const cmsMockData = { + Result: [ + { + Type: "PageHeaderWidget", + Model: { + HeaderText: "{globalState:order.vehicle.year}", + }, + }, + ], + }; + + dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData })); + + fetchCmsContentForPage("testPage").then((response) => { + // Assert + expect(response.PageHeaderWidget[0].HeaderText).toEqual( + "2019" + ); + }); }); }); + +describe("cms-content-helper.js", () => { + it("Should replace strings for global state, and leave others the same", () => { + + const cmsMockData = { + Result: [ + { + Type: "PageHeaderWidget", + Model: { + HeaderText: "{globalState:order.vehicle.year}", + }, + }, + { + Type: "RadioQuestionWidget", + Model: { + ExampleText: "My widget value!", + }, + }, + ], + }; + + dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData })); + + fetchCmsContentForPage("testPage").then((response) => { + // Assert + expect(response.PageHeaderWidget[0].HeaderText).toEqual("2019"); + expect(response.RadioQuestionWidget[0].ExampleText).toEqual("My widget value!"); + + }); + }); +}); + +describe("cms-content-helper.js", () => { + it("Should replace strings for global state in nested objects", () => { + + const cmsMockData = { + Result: [ + { + Type: "PageHeaderWidget", + Model: { + HeaderText: "{globalState:order.vehicle.year}", + }, + }, + { + Type: "RadioQuestionWidget", + Model: { + OtherObjectInside: { + ExampleText: "{globalState:order.vehicle.make}", + } + }, + }, + ], + }; + + dispatch.mockImplementation(() => Promise.resolve({ data: cmsMockData })); + + fetchCmsContentForPage("testPage").then((response) => { + // Assert + + expect(response.PageHeaderWidget[0].HeaderText).toEqual("2019"); + expect(response.RadioQuestionWidget[0].OtherObjectInside.ExampleText).toEqual("Acura"); + + }); + }); +}); + +test.todo("String cannot be mapped to global state"); \ No newline at end of file