Merge branch 'develop' into feature/Digital/SSR-51
This commit is contained in:
commit
17ff7506cb
12 changed files with 397 additions and 5 deletions
|
|
@ -45,6 +45,7 @@ import buttonBack from "@/common-components/site-sub-header/button-back/button-b
|
|||
<style lang="scss" scoped>
|
||||
h5 {
|
||||
line-height: 32px;
|
||||
max-width: 300px;
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ const endpoints = {
|
|||
method: "GET",
|
||||
},
|
||||
GetVehicleModels: {
|
||||
url: "/vehicle/api/v1/vehicle/Models",
|
||||
url: "/vehicle/api/v1/vehicle/models",
|
||||
method: "GET",
|
||||
},
|
||||
GetVehicleStyles: {
|
||||
url: "/vehicle/api/v1/vehicle/styles",
|
||||
method: "GET",
|
||||
},
|
||||
GetPageData: {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ function setupMocks({
|
|||
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
||||
makeQuestionWrapper.vm.initializeComponent =
|
||||
makeQuestion.methods.initializeComponent;
|
||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
|
||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
|
||||
watch: {
|
||||
selectedModel(model) {
|
||||
this.mainStore.updateVehicleYear(model);
|
||||
this.mainStore.updateVehicleModel(model);
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.SELECTED_MODEL,
|
||||
this.$route
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
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 { useMainStore } from "@/store";
|
||||
|
||||
|
||||
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, initialData);
|
||||
|
||||
//Assert
|
||||
const buttonQuestionComponent = await wrapper.findComponent({
|
||||
name: "buttonQuestion",
|
||||
});
|
||||
expect(buttonQuestionComponent.attributes("answers")).toBe("2 Door,4 Door");
|
||||
});
|
||||
|
||||
test("Selected style is emitted upon selection.", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: "2 Door" });
|
||||
const styleToSelect = "4 Door";
|
||||
|
||||
//Act
|
||||
wrapper.setValue({ selectedStyle: styleToSelect });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ selectedStyle: "4 Door" }]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = "1900",
|
||||
cmsQuestionText = "CMS text goes here",
|
||||
dataFromStoreApi = [],
|
||||
}) {
|
||||
//Mock store
|
||||
const mountOptions = getMountOptions();
|
||||
|
||||
const store = useMainStore();
|
||||
store.getVehicleStyles = jest.fn(() => dataFromStoreApi);
|
||||
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp,
|
||||
};
|
||||
const wrapper = shallowMount(styleQuestion, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
QuestionText: cmsQuestionText,
|
||||
};
|
||||
return { wrapper, cmsContent };
|
||||
}
|
||||
54
src/layouts/vehicle-style/style-question/style-question.vue
Normal file
54
src/layouts/vehicle-style/style-question/style-question.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<buttonQuestion
|
||||
class="radioQuestion"
|
||||
isOverflowScrollable
|
||||
selectingInitiatesLoad
|
||||
:questionText="questionText"
|
||||
:answers="styles"
|
||||
groupName="ChooseVehicleStyle"
|
||||
textPosition="text-start"
|
||||
v-model="selectedValue"
|
||||
isRequired />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
export default {
|
||||
name: "style-question",
|
||||
data() {
|
||||
return {
|
||||
styles: [],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
modelValue: String,
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
},
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
},
|
||||
methods: {
|
||||
loadInitialData() {
|
||||
return useMainStore().getVehicleStyles();
|
||||
},
|
||||
initializeComponent(initialData) {
|
||||
this.styles = initialData;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
145
src/layouts/vehicle-style/vehicle-style.spec.js
Normal file
145
src/layouts/vehicle-style/vehicle-style.spec.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// Supporting files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { nextTick } from "vue";
|
||||
import { useMainStore } from "@/store";
|
||||
|
||||
// Components
|
||||
import vehicleStyle from "@/layouts/vehicle-style/vehicle-style.vue";
|
||||
import styleQuestion from "@/layouts/vehicle-style/style-question/style-question";
|
||||
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("vehicle-style.vue", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("Style question component is initized with api data", async () => {
|
||||
//Arrange
|
||||
const styleQuestionInitialData = ["2 Door", "4 Door"];
|
||||
const { wrapper, apiPromise } = setupMocks({
|
||||
styleQuestionInitialData: styleQuestionInitialData,
|
||||
});
|
||||
|
||||
//Act
|
||||
vehicleStyle.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { issPage: "vehicle-style" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
//Assert
|
||||
apiPromise.finally(() => {
|
||||
expect(styleQuestion.methods.initializeComponent).toHaveBeenCalledWith(
|
||||
styleQuestionInitialData
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("BackButtonAction triggers a router.navigate change", async () => {
|
||||
//Arrange
|
||||
const { wrapper, apiPromise } = setupMocks({
|
||||
pageHeaderWidgetHeaderText: "Select a style to get started",
|
||||
mountOptionsMockData: {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
vehicleStyle.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { issPage: "vehicle-style" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
wrapper.vm.backButtonAction();
|
||||
await nextTick();
|
||||
|
||||
//Assert
|
||||
apiPromise.finally(() => {
|
||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test("Model set, arePagePrerequisitesValid should be true ", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
useMainStore().order.vehicle = { year: 2011, make: "ford", model: "mustang", style: null }
|
||||
|
||||
//Act
|
||||
vehicleStyle.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { issPage: "vehicle-style" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(arePagePrerequisitesValid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
vehicleStyleQuestionCmsContent = {},
|
||||
styleQuestionInitialData = {},
|
||||
pageHeaderWidgetHeaderText = {},
|
||||
mountOptionsMockData = {},
|
||||
}) {
|
||||
//Mock api responses
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
SiteSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
VehicleStyleQuestion: vehicleStyleQuestionCmsContent,
|
||||
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);
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
//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;
|
||||
|
||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
95
src/layouts/vehicle-style/vehicle-style.vue
Normal file
95
src/layouts/vehicle-style/vehicle-style.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="page-container-grouped-styles">
|
||||
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||
<div class="select-car">
|
||||
<div class="select-car-form rounded text-center">
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
|
||||
<siteSubHeader
|
||||
cmsWidgetName="SiteSubHeaderWidget"
|
||||
:hasBackButton="true"
|
||||
@click-event="backButtonAction" />
|
||||
<div class="fade-on-route-transition">
|
||||
<styleQuestion
|
||||
v-model="selectedStyle"
|
||||
ref="styleQuestion"
|
||||
cmsWidgetName="VehicleStyleQuestion" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import styleQuestion from "@/layouts/vehicle-style/style-question/style-question";
|
||||
import siteHeader from "@/common-components/site-header/site-header";
|
||||
import siteSubHeader from "@/common-components/site-sub-header/site-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 {
|
||||
selectedStyle: null,
|
||||
};
|
||||
},
|
||||
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||
const styleQuestionInitialDataPromise = styleQuestion.methods.loadInitialData();
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "styleQuestionInitialData",
|
||||
promise: styleQuestionInitialDataPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.$refs.styleQuestion.initializeComponent(resultMap.styleQuestionInitialData);
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
backButtonAction() {
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
if (this.mainStore.order.vehicle.model) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
selectedStyle(style) {
|
||||
this.mainStore.updateVehicleStyle(style);
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.SELECTED_STYLE,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
styleQuestion,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
vehicleBanner,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -3,6 +3,7 @@ export const issPageValues = {
|
|||
WELCOME_PAGE: "welcome-page",
|
||||
VEHICLE_MAKE: "vehicle-make",
|
||||
VEHICLE_YEAR: "vehicle-year",
|
||||
VEHICLE_MODEL: "vehicle-model"
|
||||
VEHICLE_MODEL: "vehicle-model",
|
||||
VEHICLE_STYLE: "vehicle-style",
|
||||
};
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ const navigationScenarios = {
|
|||
SELECTED_YEAR: "SELECTED_YEAR",
|
||||
SELECTED_MAKE: "SELECTED_MAKE",
|
||||
SELECTED_MODEL: "SELECTED_MODEL",
|
||||
SELECTED_STYLE: "SELECTED_STYLE",
|
||||
|
||||
// TESTING
|
||||
CLICKED_TEST: "CLICKED_TEST"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,20 @@ const routingTable = function(store) {
|
|||
},
|
||||
{
|
||||
scenario: navigationScenarios.SELECTED_MODEL,
|
||||
destinationUrl: "https://www.google.com"
|
||||
destinationIssPageValue: issPageValues.VEHICLE_STYLE,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
issPageValue: issPageValues.VEHICLE_STYLE,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_MODEL,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.SELECTED_STYLE,
|
||||
destinationIssPageValue: issPageValues.WELCOME_PAGE,
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -176,6 +176,14 @@ export const useMainStore = defineStore({
|
|||
});
|
||||
},
|
||||
|
||||
getVehicleStyles() {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleStyles.method,
|
||||
endpoint: `${endpoints.GetVehicleStyles.url}/${this.order.vehicle.year}/${this.order.vehicle.make}/${this.order.vehicle.model}`,
|
||||
payload: {},
|
||||
});
|
||||
},
|
||||
|
||||
// Vehicle API Actions
|
||||
updateVehicleYear(year)
|
||||
{
|
||||
|
|
@ -198,6 +206,13 @@ export const useMainStore = defineStore({
|
|||
}
|
||||
},
|
||||
|
||||
updateVehicleStyle(style)
|
||||
{
|
||||
if (this.order.vehicle.style !== style) {
|
||||
this.order.vehicle.style = style;
|
||||
}
|
||||
},
|
||||
|
||||
// populate initial state
|
||||
populateInitialState()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue