Merge branch 'develop' into CSR-241-button-updates
This commit is contained in:
commit
acd40530df
36 changed files with 1502 additions and 126 deletions
|
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
statements: 70,
|
statements: 85,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,15 @@
|
||||||
<legend class="sr-only">{{groupName}}</legend>
|
<legend class="sr-only">{{groupName}}</legend>
|
||||||
<listButton v-for="answer in answers" :key="answer"
|
<listButton v-for="answer in answers" :key="answer"
|
||||||
:buttonID="answer"
|
:buttonID="answer"
|
||||||
@click="chooseAnswer(answer)"
|
@mouseup="chooseAnswer(answer)"
|
||||||
|
@keyup.space="chooseAnswer(answer)"
|
||||||
loaderColor="blue"
|
loaderColor="blue"
|
||||||
loaderPosition="right"
|
loaderPosition="right"
|
||||||
sizeInRem="1.5"
|
sizeInRem="1.5"
|
||||||
data-test="button"
|
data-test="button"
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
textPosition="text-start"
|
textPosition="text-start"
|
||||||
isRequired=true
|
:isRequired="true"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
screenReaderOnlyText="(opens new window)"
|
screenReaderOnlyText="(opens new window)"
|
||||||
/>
|
/>
|
||||||
|
|
@ -46,3 +47,15 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.button_question {
|
||||||
|
height: calc(100vh - 280px);
|
||||||
|
|
||||||
|
.car_list {
|
||||||
|
// Height will be determined by overall height of content above list
|
||||||
|
height: calc(100% - 320px);
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
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", () => {
|
test("renders the logo image", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(siteHeader, {
|
const wrapper = shallowMount(funnelHeader, {
|
||||||
propsData: {
|
propsData: {
|
||||||
imageSrc: "image_url",
|
imageSrc: "image_url",
|
||||||
},
|
},
|
||||||
|
|
@ -9,11 +9,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "site-header",
|
name: "funnel-header",
|
||||||
props: {
|
props: {
|
||||||
imageSrc: {
|
imageSrc: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import FunnelSubHeader from "./funnel-sub-header";
|
||||||
|
|
||||||
|
describe("FunnelSubHeader.vue", () => {
|
||||||
|
it("Should render the 'text' prop value as a span value for the header span text value.", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(FunnelSubHeader, {
|
||||||
|
propsData: {
|
||||||
|
text: "FunnelSubHeader Content",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.find("h2").text()).toContain("FunnelSubHeader Content");
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
it("Should render the button inside of header if backButtonAction provided", () => {
|
||||||
|
// Act
|
||||||
|
const testFn = () => {}
|
||||||
|
const wrapper = shallowMount(FunnelSubHeader, {
|
||||||
|
propsData: {
|
||||||
|
text: "FunnelSubHeader Content",
|
||||||
|
hasBackButton: true,
|
||||||
|
backButtonAction: testFn,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.find("h2").find("button").text()).toContain("FunnelSubHeader Content");
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<div class="current_car_info-text">
|
||||||
|
<div class="d-flex align-items-center justify-content-center">
|
||||||
|
<h2 class="text-center fs-5 d-block fw-normal mb-0">
|
||||||
|
<button v-if="hasBackButton && backButtonAction" @click="backButtonAction">
|
||||||
|
{{ text }}
|
||||||
|
</button>
|
||||||
|
<span v-else>
|
||||||
|
{{ text }}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<buttonBack
|
||||||
|
v-if="hasBackButton"
|
||||||
|
:backButtonAccessibleText="backButtonAccessibleText"
|
||||||
|
:backButtonAction="backButtonAction"
|
||||||
|
:backButtonUrl="backButtonUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import buttonBack from "@/ux-components/button-back/button-back";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FunnelSubHeader",
|
||||||
|
props: {
|
||||||
|
text: String,
|
||||||
|
hasBackButton: Boolean,
|
||||||
|
backButtonUrl: String,
|
||||||
|
backButtonAccessibleText: String,
|
||||||
|
backButtonAction: Function,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
buttonBack,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
h2 {
|
||||||
|
color: $gray-550;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.back-button-wrapper {
|
||||||
|
margin-left: .25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vehicle_banner mb-4 text-center">
|
<div class="vehicle_banner mb-3 text-center">
|
||||||
<img
|
<img
|
||||||
class="vehicle-image img-fluid blurrycar"
|
class="vehicle-image img-fluid blurrycar"
|
||||||
:src="vehicleImageSrc"
|
:src="vehicleImageSrc"
|
||||||
|
|
@ -15,6 +15,7 @@ export default {
|
||||||
vehicleImageSrc: {
|
vehicleImageSrc: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
const storeMutations = {
|
const storeMutations = {
|
||||||
UPDATE_YEAR: "updateYear",
|
UPDATE_YEAR: "updateYear",
|
||||||
|
UPDATE_MAKE: "updateMake",
|
||||||
|
UPDATE_MODEL: "updateModel",
|
||||||
|
UPDATE_STYLE: "updateStyle",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { storeMutations };
|
export { storeMutations };
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,92 @@ export function fetchCmsContentForPage(fmgPage) {
|
||||||
const pageDataFromCms = {};
|
const pageDataFromCms = {};
|
||||||
|
|
||||||
response.data.Result.forEach((widget) => {
|
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 we already have this widget, push it on the collection
|
||||||
if (widget.Type in pageDataFromCms) {
|
if (widgetWithReplacements.Type in pageDataFromCms) {
|
||||||
pageDataFromCms[widget.Type].push(widget.Model);
|
pageDataFromCms[widgetWithReplacements.Type].push(widgetWithReplacements.Model);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageDataFromCms[widget.Type] = [widget.Model];
|
pageDataFromCms[widgetWithReplacements.Type] = [widgetWithReplacements.Model];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return pageDataFromCms;
|
return pageDataFromCms;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to convert a string, into a matching global state item.
|
||||||
|
function mapStringToState(str) {
|
||||||
|
|
||||||
|
// Pull all matches out of the string.
|
||||||
|
const regexExp = new RegExp('{(.*?):(.*?)}', 'g');
|
||||||
|
const matches = [...str.matchAll(regexExp)];
|
||||||
|
|
||||||
|
// Our final string value that will be built from the matches.
|
||||||
|
let stringBuilder = '';
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
|
||||||
|
// Reset store state for each match.
|
||||||
|
let storeState = store.state;
|
||||||
|
|
||||||
|
for (const s of match[2].split('.')) {
|
||||||
|
if (storeState[s] != undefined) {
|
||||||
|
storeState = storeState[s];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate the string.
|
||||||
|
stringBuilder = `${stringBuilder} ${storeState}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.trimStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
@ -3,50 +3,138 @@ import { dispatch } from "@/store";
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
dispatch: jest.fn(),
|
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
|
describe("cms-content-helper.js", () => {
|
||||||
expect(response.PageHeaderWidget[0].HeaderText).toEqual(
|
it("Should replace strings for global state", () => {
|
||||||
"Select a year to get started"
|
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");
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import { storeMutations } from "@/constants/store-mutations.js";
|
import { storeMutations } from "@/constants/store-mutations.js";
|
||||||
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
||||||
|
|
||||||
export function getMountOptions(mockData) {
|
export function getMountOptions(mockData) {
|
||||||
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
||||||
|
|
@ -21,6 +22,7 @@ export function getMountOptions(mockData) {
|
||||||
// Mock const files
|
// Mock const files
|
||||||
mocks.storeActions = storeActions;
|
mocks.storeActions = storeActions;
|
||||||
mocks.storeMutations = storeMutations;
|
mocks.storeMutations = storeMutations;
|
||||||
|
mocks.navigationScenarios = navigationScenarios;
|
||||||
|
|
||||||
// Mock $store and $router when accessing this.$store/$router
|
// Mock $store and $router when accessing this.$store/$router
|
||||||
mocks.$store = mockData.store;
|
mocks.$store = mockData.store;
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col my-3 d-flex align-items-center">
|
<div class="col my-3 d-flex align-items-center">
|
||||||
<buttonBack/>
|
<buttonBack
|
||||||
|
backButtonUrl="#"
|
||||||
|
backButtonAccessibleText="Back button label"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-4">
|
<div class="row my-4">
|
||||||
|
|
@ -573,7 +576,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<siteHeader
|
<funnelHeader
|
||||||
imageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3"
|
imageSrc="https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -601,7 +604,7 @@ import listCard from "@/ux-components/list-card/list-card";
|
||||||
import listButton from "@/ux-components/list-button/list-button";
|
import listButton from "@/ux-components/list-button/list-button";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
import siteHeader from "@/common-components/site-header/site-header";
|
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||||
import checkbox from "@/ux-components/checkbox/checkbox";
|
import checkbox from "@/ux-components/checkbox/checkbox";
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -616,7 +619,8 @@ export default {
|
||||||
vehicleBanner,
|
vehicleBanner,
|
||||||
listButtonHorizontal,
|
listButtonHorizontal,
|
||||||
siteHeader,
|
siteHeader,
|
||||||
checkbox
|
checkbox,
|
||||||
|
funnelHeader,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
82
src/layouts/vehicle-make/make-question/make-question.spec.js
Normal file
82
src/layouts/vehicle-make/make-question/make-question.spec.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import makeQuestion from "@/layouts/vehicle-make/make-question/make-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("make-question.vue", () => {
|
||||||
|
test("Selected make is emitted upon selection.", async () => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks({ modelValueProp: "honda" });
|
||||||
|
const makeToSelect = "ford";
|
||||||
|
|
||||||
|
//Act
|
||||||
|
wrapper.setData({ selectedMake: makeToSelect });
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["ford"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("make-question.vue", () => {
|
||||||
|
test("CMS question text is used as radio question text.", async () => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const { wrapper, cmsContent } = setupMocks({ cmsQuestionText: "What make is your vehicle?" });
|
||||||
|
|
||||||
|
//Act
|
||||||
|
makeQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, null);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" });
|
||||||
|
expect(buttonQuestionComponent.attributes("questiontext")).toBe("What make is your vehicle?");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("make-question.vue", () => {
|
||||||
|
test("Data from store api are used as radio question answers.", async () => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const { wrapper, cmsContent } = setupMocks({ dataFromStoreApi: ["honda", "ford", "dodge"] });
|
||||||
|
|
||||||
|
//Act
|
||||||
|
const initialData = makeQuestion.methods.loadInitialData.call(wrapper.vm);
|
||||||
|
makeQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, initialData);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
const buttonQuestionComponent = await wrapper.findComponent({ name: "buttonQuestion" });
|
||||||
|
expect(buttonQuestionComponent.attributes("answers")).toBe("honda,ford,dodge");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function setupMocks({
|
||||||
|
modelValueProp = "1900",
|
||||||
|
cmsQuestionText = "CMS text goes here",
|
||||||
|
dataFromStoreApi = [],
|
||||||
|
}) {
|
||||||
|
|
||||||
|
//Mock store
|
||||||
|
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||||
|
store.getters = { vehicle: {year: 2019} };
|
||||||
|
const mountOptions = getMountOptions({
|
||||||
|
store: {
|
||||||
|
dispatch: store.dispatch,
|
||||||
|
getters: store.getters,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//Mock props
|
||||||
|
mountOptions.propsData = {
|
||||||
|
modelValue: modelValueProp,
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(makeQuestion, mountOptions);
|
||||||
|
|
||||||
|
//Mock CMS content
|
||||||
|
const cmsContent = {
|
||||||
|
QuestionText: cmsQuestionText
|
||||||
|
};
|
||||||
|
return { wrapper, cmsContent };
|
||||||
|
}
|
||||||
46
src/layouts/vehicle-make/make-question/make-question.vue
Normal file
46
src/layouts/vehicle-make/make-question/make-question.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<buttonQuestion class="radioQuestion"
|
||||||
|
:questionText="questionText"
|
||||||
|
:answers="makes"
|
||||||
|
groupName="Choose Vehicle Make"
|
||||||
|
v-model="selectedMake"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||||
|
// Supporting files
|
||||||
|
import store from "@/store";
|
||||||
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "make-question",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
questionText: null,
|
||||||
|
selectedMake: null,
|
||||||
|
makes: Array,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
buttonQuestion,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadInitialData() {
|
||||||
|
return store.dispatch(storeActions.GET_VEHICLE_MAKES, {year: store.getters.vehicle.year});
|
||||||
|
},
|
||||||
|
initializeComponent(cmsContent, initialData) {
|
||||||
|
this.questionText = cmsContent.QuestionText;
|
||||||
|
this.makes = initialData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedMake(val) {
|
||||||
|
this.$emit("update:modelValue", val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
120
src/layouts/vehicle-make/vehicle-make.spec.js
Normal file
120
src/layouts/vehicle-make/vehicle-make.spec.js
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { shallowMount, flushPromises } from "@vue/test-utils";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue";
|
||||||
|
import makeQuestion from "@/layouts/vehicle-make/make-question/make-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-make.vue", () => {
|
||||||
|
test("Make question component is initized with api data", async (done) => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const radioQuestionCmsContent = { QuestionText: "What make is your vehicle?" };
|
||||||
|
const makeQuestionInitialData = ["honda", "ford", "dodge"];
|
||||||
|
const { wrapper, apiPromise } = setupMocks( {
|
||||||
|
radioQuestionCmsContent: radioQuestionCmsContent,
|
||||||
|
makeQuestionInitialData: makeQuestionInitialData,
|
||||||
|
} );
|
||||||
|
|
||||||
|
//Act
|
||||||
|
vehicleMake.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-make" } }, undefined, (c) => c(wrapper.vm));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
apiPromise.finally(() => {
|
||||||
|
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(radioQuestionCmsContent, makeQuestionInitialData);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("vehicle-make.vue", () => {
|
||||||
|
test("Page header is passed data from CMS", async (done) => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a make to get started" });
|
||||||
|
|
||||||
|
//Act
|
||||||
|
vehicleMake.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-make" } }, undefined, (c) => c(wrapper.vm));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
const header = await wrapper.find(".Header");
|
||||||
|
apiPromise.finally(() => {
|
||||||
|
expect(header.attributes("text")).toEqual("Select a make to get started");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("vehicle-make.vue", () => {
|
||||||
|
test("BackButtonAction triggers a router.navigate change", async (done) => {
|
||||||
|
|
||||||
|
//Arrange
|
||||||
|
const { wrapper, apiPromise } = setupMocks( {
|
||||||
|
pageHeaderWidgetHeaderText: "Select a make to get started",
|
||||||
|
mountOptionsMockData: {
|
||||||
|
router: {
|
||||||
|
navigate: jest.fn()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//Act
|
||||||
|
vehicleMake.beforeRouteEnter.call(wrapper.vm, { query: { fmgPage: "vehicle-make" } }, undefined, (c) => c(wrapper.vm));
|
||||||
|
wrapper.vm.backButtonAction();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
apiPromise.finally(() => {
|
||||||
|
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks({
|
||||||
|
radioQuestionCmsContent = {},
|
||||||
|
makeQuestionInitialData = {},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
makeQuestionInitialData: makeQuestionInitialData,
|
||||||
|
};
|
||||||
|
const apiPromise = Promise.resolve(apiResponses);
|
||||||
|
settleAllPromises.mockImplementation(() => apiPromise);
|
||||||
|
|
||||||
|
//Mock make question methods
|
||||||
|
makeQuestion.methods = {
|
||||||
|
loadInitialData: jest.fn(),
|
||||||
|
initializeComponent: jest.fn(),
|
||||||
|
};
|
||||||
|
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||||
|
const wrapper = shallowMount(vehicleMake, mountOptions);
|
||||||
|
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
||||||
|
makeQuestionWrapper.vm.initializeComponent = makeQuestion.methods.initializeComponent;
|
||||||
|
|
||||||
|
return { wrapper, apiPromise };
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,90 @@
|
||||||
<template>
|
<template>
|
||||||
<p> Vehicle Make Layout - Do Something Cool Here </p>
|
<div class="container-fluid shadow rounded-3 p-0">
|
||||||
</template>
|
<funnelHeader :imageSrc="siteHeaderWidget.LogoImage" />
|
||||||
|
<div class="select-car">
|
||||||
|
<div class="select-car-form rounded text-center">
|
||||||
|
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
|
||||||
|
<funnelSubHeader
|
||||||
|
:text="pageHeaderWidgets.HeaderText"
|
||||||
|
:hasBackButton="true"
|
||||||
|
backButtonAccessibleText="Change Vehicle Year"
|
||||||
|
:backButtonAction="backButtonAction"
|
||||||
|
class="Header"
|
||||||
|
/>
|
||||||
|
<makeQuestion v-model="selectedMake" ref="makeQuestion" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import makeQuestion from "@/layouts/vehicle-make/make-question/make-question";
|
||||||
|
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
// Supporting files
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
export default {
|
||||||
|
name: "vehicle-make",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pageHeaderWidgets: {},
|
||||||
|
siteHeaderWidget: {},
|
||||||
|
vehicleBannerWidget: {},
|
||||||
|
selectedMake: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
const makeQuestionInitialDataPromise = makeQuestion.methods.loadInitialData();
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultKey: "makeQuestionInitialData",
|
||||||
|
promise: makeQuestionInitialDataPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
settleAllPromises(promiseResultMap).then((resultMap) => {
|
||||||
|
// Call the "next" function to complete the transition to this page.
|
||||||
|
next((vm) => {
|
||||||
|
vm.pageHeaderWidgets = resultMap.cmsContent.PageHeaderWidget[0];
|
||||||
|
vm.siteHeaderWidget = resultMap.cmsContent.SiteHeaderWidget[0];
|
||||||
|
vm.vehicleBannerWidget = resultMap.cmsContent.VehicleBannerWidget[0];
|
||||||
|
vm.$refs.makeQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.makeQuestionInitialData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
backButtonAction: function () {
|
||||||
|
// route to move backwards
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
selectedMake(make) {
|
||||||
|
this.$store.commit(this.storeMutations.UPDATE_MAKE, make);
|
||||||
|
this.$router.navigate(this.navigationScenarios.SELECTED_MAKE, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
makeQuestion,
|
||||||
|
funnelHeader,
|
||||||
|
vehicleBanner,
|
||||||
|
funnelSubHeader,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
}
|
||||||
46
src/layouts/vehicle-model/model-question/model-question.vue
Normal file
46
src/layouts/vehicle-model/model-question/model-question.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<buttonQuestion class="radioQuestion"
|
||||||
|
:questionText="questionText"
|
||||||
|
:answers="models"
|
||||||
|
groupName="Choose Vehicle Model"
|
||||||
|
v-model="selectedModel"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||||
|
// Supporting files
|
||||||
|
import store from "@/store";
|
||||||
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "model-question",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
questionText: null,
|
||||||
|
selectedModel: null,
|
||||||
|
models: Array,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
buttonQuestion,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadInitialData() {
|
||||||
|
return store.dispatch(storeActions.GET_VEHICLE_MODELS, {year: store.getters.vehicle.year, make: encodeURIComponent(store.getters.vehicle.make)});
|
||||||
|
},
|
||||||
|
initializeComponent(cmsContent, initialData) {
|
||||||
|
this.questionText = cmsContent.QuestionText;
|
||||||
|
this.models = initialData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedModel(val) {
|
||||||
|
this.$emit("update:modelValue", val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
93
src/layouts/vehicle-model/vehicle-model.spec.js
Normal file
93
src/layouts/vehicle-model/vehicle-model.spec.js
Normal file
|
|
@ -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 };
|
||||||
|
}
|
||||||
90
src/layouts/vehicle-model/vehicle-model.vue
Normal file
90
src/layouts/vehicle-model/vehicle-model.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="container-fluid shadow rounded-3 p-0">
|
||||||
|
<funnelHeader :imageSrc="siteHeaderWidget.LogoImage" />
|
||||||
|
<div class="select-car">
|
||||||
|
<div class="select-car-form rounded text-center">
|
||||||
|
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
|
||||||
|
<funnelSubHeader
|
||||||
|
:text="pageHeaderWidgets.HeaderText"
|
||||||
|
:hasBackButton="true"
|
||||||
|
backButtonAccessibleText="Change Vehicle Make"
|
||||||
|
:backButtonAction="backButtonAction"
|
||||||
|
class="Header"
|
||||||
|
/>
|
||||||
|
<modelQuestion v-model="selectedModel" ref="modelQuestion" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import modelQuestion from "@/layouts/vehicle-model/model-question/model-question";
|
||||||
|
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
|
// Supporting files
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
export default {
|
||||||
|
name: "vehicle-model",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pageHeaderWidgets: {},
|
||||||
|
siteHeaderWidget: {},
|
||||||
|
vehicleBannerWidget: {},
|
||||||
|
selectedModel: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
const modelQuestionInitialDataPromise = modelQuestion.methods.loadInitialData();
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultKey: "modelQuestionInitialData",
|
||||||
|
promise: modelQuestionInitialDataPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
settleAllPromises(promiseResultMap).then((resultMap) => {
|
||||||
|
// Call the "next" function to complete the transition to this page.
|
||||||
|
next((vm) => {
|
||||||
|
vm.pageHeaderWidgets = resultMap.cmsContent.PageHeaderWidget[0];
|
||||||
|
vm.siteHeaderWidget = resultMap.cmsContent.SiteHeaderWidget[0];
|
||||||
|
vm.vehicleBannerWidget = resultMap.cmsContent.VehicleBannerWidget[0];
|
||||||
|
vm.$refs.modelQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.modelQuestionInitialData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
backButtonAction: function () {
|
||||||
|
// route to move backwards
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
selectedModel(model) {
|
||||||
|
this.$store.commit(this.storeMutations.UPDATE_MODEL, model);
|
||||||
|
this.$router.navigate(this.navigationScenarios.SELECTED_MODEL, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
modelQuestion,
|
||||||
|
funnelHeader,
|
||||||
|
funnelSubHeader,
|
||||||
|
vehicleBanner,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -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 };
|
||||||
|
}
|
||||||
46
src/layouts/vehicle-style/style-question/style-question.vue
Normal file
46
src/layouts/vehicle-style/style-question/style-question.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<buttonQuestion class="radioQuestion"
|
||||||
|
:questionText="questionText"
|
||||||
|
:answers="styles"
|
||||||
|
groupName="Choose Vehicle Style"
|
||||||
|
v-model="selectedStyle"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||||
|
// Supporting files
|
||||||
|
import store from "@/store";
|
||||||
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "style-question",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
questionText: null,
|
||||||
|
selectedStyle: null,
|
||||||
|
styles: Array,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
buttonQuestion,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadInitialData() {
|
||||||
|
return store.dispatch(storeActions.GET_VEHICLE_STYLES, {year: store.getters.vehicle.year, make: encodeURIComponent(store.getters.vehicle.make), model: encodeURIComponent(store.getters.vehicle.model)});
|
||||||
|
},
|
||||||
|
initializeComponent(cmsContent, initialData) {
|
||||||
|
this.questionText = cmsContent.QuestionText;
|
||||||
|
this.styles = initialData;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedStyle(val) {
|
||||||
|
this.$emit("update:modelValue", val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
120
src/layouts/vehicle-style/vehicle-style.spec.js
Normal file
120
src/layouts/vehicle-style/vehicle-style.spec.js
Normal file
|
|
@ -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 };
|
||||||
|
}
|
||||||
90
src/layouts/vehicle-style/vehicle-style.vue
Normal file
90
src/layouts/vehicle-style/vehicle-style.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="container-fluid shadow rounded-3 p-0">
|
||||||
|
<funnelHeader :imageSrc="siteHeaderWidget.LogoImage" />
|
||||||
|
<div class="select-car">
|
||||||
|
<div class="select-car-form rounded text-center">
|
||||||
|
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
|
||||||
|
<funnelSubHeader
|
||||||
|
:text="pageHeaderWidgets.HeaderText"
|
||||||
|
:hasBackButton="true"
|
||||||
|
backButtonAccessibleText="Change Vehicle Model"
|
||||||
|
:backButtonAction="backButtonAction"
|
||||||
|
class="Header"
|
||||||
|
/>
|
||||||
|
<styleQuestion v-model="selectedStyle" ref="styleQuestion" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import styleQuestion from "@/layouts/vehicle-style/style-question/style-question";
|
||||||
|
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
|
// Supporting files
|
||||||
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
|
export default {
|
||||||
|
name: "vehicle-style",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pageHeaderWidgets: {},
|
||||||
|
siteHeaderWidget: {},
|
||||||
|
vehicleBannerWidget: {},
|
||||||
|
selectedStyle: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
|
const styleQuestionInitialDataPromise = styleQuestion.methods.loadInitialData();
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: "cmsContent",
|
||||||
|
promise: cmsContentPromise,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resultKey: "styleQuestionInitialData",
|
||||||
|
promise: styleQuestionInitialDataPromise,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
settleAllPromises(promiseResultMap).then((resultMap) => {
|
||||||
|
// Call the "next" function to complete the transition to this page.
|
||||||
|
next((vm) => {
|
||||||
|
vm.pageHeaderWidgets = resultMap.cmsContent.PageHeaderWidget[0];
|
||||||
|
vm.siteHeaderWidget = resultMap.cmsContent.SiteHeaderWidget[0];
|
||||||
|
vm.vehicleBannerWidget = resultMap.cmsContent.VehicleBannerWidget[0];
|
||||||
|
vm.$refs.styleQuestion.initializeComponent(resultMap.cmsContent.RadioQuestionWidget[0], resultMap.styleQuestionInitialData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
backButtonAction: function () {
|
||||||
|
// route to move backwards
|
||||||
|
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
selectedStyle(style) {
|
||||||
|
this.$store.commit(this.storeMutations.UPDATE_STYLE, style);
|
||||||
|
this.$router.navigate(this.navigationScenarios.SELECTED_STYLE, this.$route);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
styleQuestion,
|
||||||
|
funnelHeader,
|
||||||
|
funnelSubHeader,
|
||||||
|
vehicleBanner,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -13,7 +13,7 @@ jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
describe("vehicle-year.vue", () => {
|
describe("vehicle-year.vue", () => {
|
||||||
test("Year question component is initized with api data", async (done) => {
|
test("Year question component is initized with api data", async (done) => {
|
||||||
|
|
||||||
//Arange
|
//Arrange
|
||||||
const radioQuestionCmsContent = { QuestionText: "What year is your vehicle?" };
|
const radioQuestionCmsContent = { QuestionText: "What year is your vehicle?" };
|
||||||
const yearQuestionInitialData = ["2023", "2022", "2021"];
|
const yearQuestionInitialData = ["2023", "2022", "2021"];
|
||||||
const { wrapper, apiPromise } = setupMocks( {
|
const { wrapper, apiPromise } = setupMocks( {
|
||||||
|
|
@ -35,7 +35,7 @@ describe("vehicle-year.vue", () => {
|
||||||
describe("vehicle-year.vue", () => {
|
describe("vehicle-year.vue", () => {
|
||||||
test("Page header is passed data from CMS", async (done) => {
|
test("Page header is passed data from CMS", async (done) => {
|
||||||
|
|
||||||
//Arange
|
//Arrange
|
||||||
const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a year to get started" });
|
const { wrapper, apiPromise } = setupMocks( { pageHeaderWidgetHeaderText: "Select a year to get started" });
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
|
|
@ -54,6 +54,7 @@ function setupMocks({
|
||||||
radioQuestionCmsContent = {},
|
radioQuestionCmsContent = {},
|
||||||
yearQuestionInitialData = {},
|
yearQuestionInitialData = {},
|
||||||
pageHeaderWidgetHeaderText = {},
|
pageHeaderWidgetHeaderText = {},
|
||||||
|
mountOptionsMockData = {},
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
//Mock api responses
|
//Mock api responses
|
||||||
|
|
@ -84,7 +85,7 @@ function setupMocks({
|
||||||
loadInitialData: jest.fn(),
|
loadInitialData: jest.fn(),
|
||||||
initializeComponent: jest.fn(),
|
initializeComponent: jest.fn(),
|
||||||
};
|
};
|
||||||
const mountOptions = getMountOptions({ });
|
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||||
const wrapper = shallowMount(vehicleYear, mountOptions);
|
const wrapper = shallowMount(vehicleYear, mountOptions);
|
||||||
const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" });
|
const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" });
|
||||||
yearQuestionWrapper.vm.initializeComponent = yearQuestion.methods.initializeComponent;
|
yearQuestionWrapper.vm.initializeComponent = yearQuestion.methods.initializeComponent;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid shadow rounded-3 p-0">
|
<div class="container-fluid shadow rounded-3 p-0">
|
||||||
<siteHeader :imageSrc="siteHeaderWidget.LogoImage" />
|
<funnelHeader :imageSrc="siteHeaderWidget.LogoImage" />
|
||||||
<div class="select-car">
|
<div class="select-car">
|
||||||
<div class="select-car-form rounded text-center">
|
<div class="select-car-form rounded text-center">
|
||||||
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
|
<vehicleBanner :vehicleImageSrc="vehicleBannerWidget.GenericVehicleImage" />
|
||||||
<pageHeader :text="pageHeaderWidgets.HeaderText" class="Header" />
|
<funnelSubHeader
|
||||||
|
:text="pageHeaderWidgets.HeaderText"
|
||||||
|
class="Header"
|
||||||
|
/>
|
||||||
<yearQuestion v-model="selectedYear" ref="yearQuestion" />
|
<yearQuestion v-model="selectedYear" ref="yearQuestion" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,14 +17,12 @@
|
||||||
<script>
|
<script>
|
||||||
// Components
|
// Components
|
||||||
import yearQuestion from "@/layouts/vehicle-year/year-question/year-question";
|
import yearQuestion from "@/layouts/vehicle-year/year-question/year-question";
|
||||||
import pageHeader from "@/ux-components/header/header";
|
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||||
import siteHeader from "@/common-components/site-header/site-header";
|
|
||||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "vehicle-year",
|
name: "vehicle-year",
|
||||||
|
|
@ -67,27 +68,15 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
selectedYear(year) {
|
selectedYear(year) {
|
||||||
this.$store.commit(this.storeMutations.UPDATE_YEAR, year);
|
this.$store.commit(this.storeMutations.UPDATE_YEAR, year);
|
||||||
this.$router.navigate(navigationScenarios.SELECTED_YEAR, this.$route);
|
this.$router.navigate(this.navigationScenarios.SELECTED_YEAR, this.$route);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
yearQuestion,
|
yearQuestion,
|
||||||
pageHeader,
|
funnelHeader,
|
||||||
siteHeader,
|
|
||||||
vehicleBanner,
|
vehicleBanner,
|
||||||
|
funnelSubHeader,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.select-car {
|
|
||||||
height: calc(100vh - 56px);
|
|
||||||
|
|
||||||
.car_list {
|
|
||||||
// Height will be determined by overall height of content above list
|
|
||||||
height: calc(100% - 300px);
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { storeActions } from "@/constants/store-actions.js";
|
import { storeActions } from "@/constants/store-actions.js";
|
||||||
import { storeMutations } from "@/constants/store-mutations.js";
|
import { storeMutations } from "@/constants/store-mutations.js";
|
||||||
|
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||||
import { widgetNames } from "@/constants/widget-names.js";
|
import { widgetNames } from "@/constants/widget-names.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -31,6 +32,9 @@ export default {
|
||||||
storeMutations() {
|
storeMutations() {
|
||||||
return storeMutations;
|
return storeMutations;
|
||||||
},
|
},
|
||||||
|
navigationScenarios() {
|
||||||
|
return navigationScenarios;
|
||||||
|
},
|
||||||
widgetNames() {
|
widgetNames() {
|
||||||
return widgetNames;
|
return widgetNames;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const navigationScenarios = {
|
||||||
SELECTED_YEAR: "SELECTED_YEAR",
|
SELECTED_YEAR: "SELECTED_YEAR",
|
||||||
SELECTED_MODEL: "SELECTED_MODEL",
|
SELECTED_MODEL: "SELECTED_MODEL",
|
||||||
SELECTED_MAKE: "SELECTED_MAKE",
|
SELECTED_MAKE: "SELECTED_MAKE",
|
||||||
|
SELECTED_STYLE: "SELECTED_STYLE",
|
||||||
CLICKED_BACK: "CLICKED_BACK",
|
CLICKED_BACK: "CLICKED_BACK",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,19 @@ const routingTable = [
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fmgPageValue: fmgPageValues.VEHICLE_STYLE,
|
||||||
|
maps: [
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.SELECTED_STYLE,
|
||||||
|
destinationFmgPageValue: fmgPageValues.VEHICLE_STYLE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_BACK,
|
||||||
|
destinationFmgPageValue: fmgPageValues.VEHICLE_MODEL,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export { routingTable };
|
export { routingTable };
|
||||||
|
|
@ -64,6 +64,18 @@ export default createStore({
|
||||||
updateYear(state, year) {
|
updateYear(state, year) {
|
||||||
state.order.vehicle.year = year;
|
state.order.vehicle.year = year;
|
||||||
},
|
},
|
||||||
|
updateMake(state, make) {
|
||||||
|
state.order.vehicle.make = make;
|
||||||
|
},
|
||||||
|
updateModel(state, model) {
|
||||||
|
state.order.vehicle.model = model;
|
||||||
|
},
|
||||||
|
updateStyle(state, style) {
|
||||||
|
state.order.vehicle.style = style;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
vehicle: state => state.order.vehicle
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// Vehicle API Actions
|
// Vehicle API Actions
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,73 @@ describe("Actions", () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(pageData).toBe("Page Info Data");
|
expect(pageData).toBe("Page Info Data");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should return data from url retrieved", async () => {
|
||||||
|
// Arrange
|
||||||
|
let returnData = [];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
globalMethods.callHttpClient.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
Result: "2018 Honda Civic",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await store
|
||||||
|
.dispatch("lookupVehicleByYmms", { year: "2018", make: "Honda", model: "Civic", style: "2 Door"})
|
||||||
|
.then((response) => {
|
||||||
|
returnData = response.data.Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(returnData).toBe("2018 Honda Civic");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return vehicle data from url retrieved", async () => {
|
||||||
|
// Arrange
|
||||||
|
let returnData = [];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
globalMethods.callHttpClient.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
Result: "2021 Honda Civic",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await store
|
||||||
|
.dispatch("lookupVehicleByVin", { vin: "12345678"})
|
||||||
|
.then((response) => {
|
||||||
|
returnData = response.data.Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(returnData).toBe("2021 Honda Civic");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return vehicle image data from url retrieved", async () => {
|
||||||
|
// Arrange
|
||||||
|
let returnData = [];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
globalMethods.callMockHttpClient = jest.fn();
|
||||||
|
globalMethods.callMockHttpClient.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
Result: "2008_honda_civic.jpg",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await store
|
||||||
|
.dispatch("getEvoxImage", { relativeUrl: "evox_image.com"})
|
||||||
|
.then((response) => {
|
||||||
|
returnData = response.data.Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(returnData).toBe("2008_honda_civic.jpg");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Mutations", () => {
|
describe("Mutations", () => {
|
||||||
|
|
@ -128,4 +195,34 @@ describe("Mutations", () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(store.state.order.vehicle.year).toBe(2020);
|
expect(store.state.order.vehicle.year).toBe(2020);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should update the make property in the store", () => {
|
||||||
|
// Act
|
||||||
|
store.commit("updateMake", "Honda");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(store.state.order.vehicle.make).toBe("Honda");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should update the model property in the store", () => {
|
||||||
|
// Act
|
||||||
|
store.commit("updateModel", "Civic");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(store.state.order.vehicle.model).toBe("Civic");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should update the style property in the store", () => {
|
||||||
|
// Act
|
||||||
|
store.commit("updateStyle", "2 Door");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(store.state.order.vehicle.style).toBe("2 Door");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Getters", () => {
|
||||||
|
const vehicle = store.getters.vehicle;
|
||||||
|
|
||||||
|
expect(typeof vehicle).toBe('object');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1 +1,23 @@
|
||||||
test.todo("some test to be written in the future");
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import buttonBack from "./button-back";
|
||||||
|
|
||||||
|
describe("back button", () => {
|
||||||
|
|
||||||
|
test("renders a button", () => {
|
||||||
|
// Arrange
|
||||||
|
const myFunction = () => {};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonBack, {
|
||||||
|
propsData: {
|
||||||
|
backButtonAction: myFunction,
|
||||||
|
backButtonAccessibleText: "something",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.find("button").exists()).toBe(true);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<button class="back-button-wrapper d-flex p-0">
|
<button
|
||||||
|
v-if="backButtonAction"
|
||||||
|
@click="backButtonAction"
|
||||||
|
class="back-button-wrapper d-flex p-0"
|
||||||
|
:aria-label="backButtonAccessibleText"
|
||||||
|
>
|
||||||
<div class="button-back">
|
<div class="button-back">
|
||||||
<div class="arrow-left"></div>
|
<div class="arrow-left"></div>
|
||||||
<div class="box"></div>
|
<div class="box"></div>
|
||||||
|
|
@ -9,7 +14,18 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "buttonBack"
|
name: "buttonBack",
|
||||||
|
props: {
|
||||||
|
backButtonAccessibleText: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
backButtonAction: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -17,6 +33,9 @@ export default {
|
||||||
.back-button-wrapper {
|
.back-button-wrapper {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: none;
|
||||||
|
|
||||||
.button-back {
|
.button-back {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
|
||||||
import Header from "./header";
|
|
||||||
|
|
||||||
describe("Header.vue", () => {
|
|
||||||
it("Should render the 'text' prop value as a span value for the header span text value.", () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(Header, {
|
|
||||||
propsData: {
|
|
||||||
text: "Header Content",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.find("h2").text()).toContain("Header Content");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="current_car_info-text">
|
|
||||||
<div class="d-flex align-items-center justify-content-center">
|
|
||||||
<h2 class="text-center fs-5 d-block fw-normal mb-0">{{ text }}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "Header",
|
|
||||||
props: {
|
|
||||||
text: String,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
h2 {
|
|
||||||
color: $gray-550;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
isMultiSelect: Boolean,
|
isMultiSelect: Boolean,
|
||||||
groupName: String, /* Required, unique for each button GROUP */
|
groupName: String, /* Required, unique for each button GROUP */
|
||||||
buttonID: String, /* Required, unique for each button. Used for button id, label and <label for> */
|
buttonID: [Number,String], /* Required, unique for each button. Used for button id, label and <label for> */
|
||||||
isRequired: Boolean, /* Optional, default is false */
|
isRequired: Boolean, /* Optional, default is false */
|
||||||
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */
|
screenReaderOnlyText: String, /* Optional, copy to be read by screenreader */
|
||||||
buttonLabelSubCopy: String, /* Optional, used for multi-line buttons */
|
buttonLabelSubCopy: String, /* Optional, used for multi-line buttons */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue