Merge pull request #29 from Safelite/feature/Digital/SSR-100
Feature/digital/ssr 100
This commit is contained in:
commit
9d7c88d438
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>
|
<style lang="scss" scoped>
|
||||||
h5 {
|
h5 {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
max-width: 300px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,11 @@ const endpoints = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
GetVehicleModels: {
|
GetVehicleModels: {
|
||||||
url: "/vehicle/api/v1/vehicle/Models",
|
url: "/vehicle/api/v1/vehicle/models",
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
GetVehicleStyles: {
|
||||||
|
url: "/vehicle/api/v1/vehicle/styles",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,8 @@ function setupMocks({
|
||||||
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
||||||
makeQuestionWrapper.vm.initializeComponent =
|
makeQuestionWrapper.vm.initializeComponent =
|
||||||
makeQuestion.methods.initializeComponent;
|
makeQuestion.methods.initializeComponent;
|
||||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
|
||||||
|
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||||
|
|
||||||
return { wrapper, apiPromise };
|
return { wrapper, apiPromise };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
selectedModel(model) {
|
selectedModel(model) {
|
||||||
this.mainStore.updateVehicleYear(model);
|
this.mainStore.updateVehicleModel(model);
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.SELECTED_MODEL,
|
this.navigationScenarios.SELECTED_MODEL,
|
||||||
this.$route
|
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",
|
WELCOME_PAGE: "welcome-page",
|
||||||
VEHICLE_MAKE: "vehicle-make",
|
VEHICLE_MAKE: "vehicle-make",
|
||||||
VEHICLE_YEAR: "vehicle-year",
|
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_YEAR: "SELECTED_YEAR",
|
||||||
SELECTED_MAKE: "SELECTED_MAKE",
|
SELECTED_MAKE: "SELECTED_MAKE",
|
||||||
SELECTED_MODEL: "SELECTED_MODEL",
|
SELECTED_MODEL: "SELECTED_MODEL",
|
||||||
|
SELECTED_STYLE: "SELECTED_STYLE",
|
||||||
|
|
||||||
// TESTING
|
// TESTING
|
||||||
CLICKED_TEST: "CLICKED_TEST"
|
CLICKED_TEST: "CLICKED_TEST"
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,20 @@ const routingTable = function(store) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: navigationScenarios.SELECTED_MODEL,
|
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,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,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
|
// Vehicle API Actions
|
||||||
updateVehicleYear(year)
|
updateVehicleYear(year)
|
||||||
{
|
{
|
||||||
|
|
@ -121,6 +129,13 @@ export const useMainStore = defineStore({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateVehicleStyle(style)
|
||||||
|
{
|
||||||
|
if (this.order.vehicle.style !== style) {
|
||||||
|
this.order.vehicle.style = style;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// populate initial state
|
// populate initial state
|
||||||
populateInitialState()
|
populateInitialState()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue