Merge branch 'develop' into feature/CSR-268

This commit is contained in:
Max 2022-02-02 11:12:20 -05:00
commit bd0d40357c
25 changed files with 1175 additions and 554 deletions

View file

@ -18,7 +18,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
statements: 87,
statements: 89,
},
},
};

View file

@ -0,0 +1,60 @@
import { mount } from "@vue/test-utils";
import funnelFooter from "./funnel-footer";
describe("funnel-footer.vue", () => {
it("Should return footer-link class", async () => {
// Act
try {
global.document.getElementById = jest.fn().mockImplementation(()=> {
return {}
});
const wrapper = mount(funnelFooter, {
propsData: {
footer: true
},
});
// Assert
const link = wrapper.find("a");
// Expect
expect(link.attributes('class')).toContain("footer-link");
expect(global.document.getElementById).toBeCalled();
} catch(error) {
console.log(error);
}
});
it("Should return link text", async () => {
// Act
const wrapper = mount(funnelFooter, {
propsData: {
text: "Terms of use",
},
});
// Assert
const link = wrapper.find("a");
expect(link.text()).toEqual("Terms of use");
});
it("Should return class btn-primary", async () => {
// Act
const wrapper = mount(funnelFooter, {
propsData: {
isPrimary: true,
},
});
// Assert
const input = wrapper.find("button");
// Expect
expect(input.attributes("class")).toContain("btn-primary");
});
});

View file

@ -0,0 +1,81 @@
<template>
<div class="container-fluid g-2 footer">
<div class="row">
<div class="col-12 d-flex justify-content-center pb-2 fs-7">
&copy;&nbsp;<span id="years"></span>&nbsp;Safelite Group
</div>
</div>
<div class="row" :style="`padding-bottom: ${paddingHeight}px`">
<div class="col d-flex justify-content-center justify-content-around text-center">
<textLink
footer=true
text="Terms of use"
href="https://www.safelite.com/terms-of-use"
/>
<textLink
footer=true
text="Privacy policy"
href="https://www.safelite.com/safelite-group-privacy-policy"
/>
<textLink
footer=true
text="Do not sell my information"
href="https://privacyportal-cdn.onetrust.com/dsarwebform/d3b95a93-e22e-4d4d-a806-482052406557/9e371601-eae3-4338-9430-b90b9036022b.html"
/>
</div>
</div>
</div>
<div class="container-fluid fixed-bottom g-4 bg-light py-4" id="infoBox">
<div class="row">
<div class="col">
<buttonMain
isPrimary
buttonText="Review Appointment Details"
loaderColor="white"
sizeInRem="1"
isFloat
/>
<textLink
navigation=true
text="back"
/>
</div>
</div>
</div>
</template>
<script>
import textLink from "@/ux-components/text-link/text-link";
import buttonMain from "@/ux-components/button-main/button-main";
export default {
name: "funnelFooter",
components: {
textLink,
buttonMain
},
data() {
return {
paddingHeight: 0
}
},
mounted() {
this.paddingHeight = document.getElementById("infoBox").offsetHeight;
this.$nextTick(() => {
window.addEventListener('resize', this.onResize);
})
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize);
},
methods: {
onResize() {
this.paddingHeight = document.getElementById("infoBox").offsetHeight;
}
}
};
setTimeout(function(){ // Give it a moment to set the date
document.getElementById('years').innerHTML += new Date().getFullYear();
}, 100);
</script>

View file

@ -11,9 +11,12 @@ const storeActions = {
GET_EVOX_IMAGE: "getEvoxImage",
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
// EVENT BUS
ADD_EVENT_TO_BUS: "addEventToBus",
REMOVE_EVENT_FROM_BUS: "removeEventFromBus",
// DEPENDENCY MUTATIONS
RESET_VEHICLE_AND_DEPS: "resetVehicleAndDependencies",
RESET_DAMAGE_AND_DEPS: "resetDamageAndDependencies",
RESET_REGISTRATION_AND_DEPS: "resetRegistrationAndDependencies",
RESET_PARTS_AND_DEPS: "resetPartsAndDependencies",
};
export { storeActions };

View file

@ -1,9 +1,23 @@
const storeMutations = {
// VEHICLE MUTATIONS
UPDATE_YEAR: "updateYear",
UPDATE_MAKE: "updateMake",
UPDATE_MODEL: "updateModel",
UPDATE_STYLE: "updateStyle",
UPDATE_VEHICLE: "updateVehicle",
UPDATE_CAR_ID: "updateCarId",
UPDATE_VEHICLE_CATEGORY: "updateVehicleCategory",
// EVENT BUS MUTATIONS
ADD_EVENT_TO_BUS: "addEventToBus",
REMOVE_EVENT_FROM_BUS: "removeEventFromBus",
// DEPENDENCY MUTATIONS
RESET_VEHICLE_AND_DEPS: "resetVehicleAndDependencies",
RESET_DAMAGE_AND_DEPS: "resetDamageAndDependencies",
RESET_REGISTRATION_AND_DEPS: "resetRegistrationAndDependencies",
RESET_PARTS_AND_DEPS: "resetPartsAndDependencies",
};
export { storeMutations };

View file

@ -1,10 +1,10 @@
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import { storeMutations } from "@/constants/store-mutations.js";
export default {
// Adds event to the bus given its category, subcategory, and eventValue;
addEventToBus(category, subCategory, eventValue) {
store.commit(storeActions.ADD_EVENT_TO_BUS, {
store.commit(storeMutations.ADD_EVENT_TO_BUS, {
category: category,
subCategory: subCategory,
eventValue: eventValue,
@ -15,7 +15,7 @@ export default {
readAndPopEventFromBus(category, subCategory) {
const event = store.getters.eventBusItem(category, subCategory);
store.commit(storeActions.REMOVE_EVENT_FROM_BUS, {
store.commit(storeMutations.REMOVE_EVENT_FROM_BUS, {
category: category,
subCategory: subCategory,
});

View file

@ -40,6 +40,31 @@
<buttonBack backButtonAccessibleText="Back button label" />
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Links</h4>
</div>
</div>
<div class="row">
<div class="col my-3">
<textLink
navigation=true
text="Navigation Link"
/><br><br>
<textLink
text="Default Link"
/><br><br>
<textLink
textSmall=true
text="Small Link"
/><br><br>
<textLink
footer=true
text="Footer Link"
href="https://google.com"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">List Card</h4>
@ -60,7 +85,7 @@
/>
<listCard
isMultiSelect
buttonImage="side-window-damage-right-all.svg"
buttonImage="https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3"
buttonLabel="Windshield"
altText=""
buttonID="List Card Checkbox 2"
@ -177,7 +202,7 @@
<listCard
isMultiSelect
isWide
buttonImage="side-window-damage-right-all.svg"
buttonImage="https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3"
buttonLabel="Side Window"
altText=""
buttonID="List Card Horizontal Checkbox b"
@ -806,95 +831,109 @@
<h4 class="m-0 p-2 bg-light rounded">Button Question</h4>
</div>
</div>
<buttonQuestion
:answers="checkboxAnswers"
:isMultiSelect="true"
ariaLabelBy="checkbox"
groupName="checkbox-list"
questionText="List Button as checkbox"
/>
<buttonQuestion
:answers="radioAnswers"
ariaLabelBy="radio"
groupName="radio-list"
questionText="List Button as radio"
/>
<buttonQuestion
:answers="horizontalCheckboxAnswers"
buttonType="listButtonHorizontal"
:isMultiSelect="true"
isColumn
ariaLabelBy="checkbox"
groupName="checkbox-list-horizontal"
questionText="List Button Horizontal as checkbox"
/>
<buttonQuestion
:answers="horizontalRadioAnswers"
buttonType="listButtonHorizontal"
isColumn
ariaLabelBy="radio"
groupName="radio-list-horizontal"
questionText="List Button Horizontal as radio"
sizeInRem="1"
/>
<buttonQuestion
:answers="listCardCheckBox"
buttonType="listCard"
isMultiSelect
ariaLabelBy="check_card"
groupName="check-list-card"
questionText="List Card as checkbox"
/>
<buttonQuestion
:answers="listCardRadio"
buttonType="listCard"
ariaLabelBy="radio_card"
groupName="radio-card"
questionText="List Card as radio"
/>
<buttonQuestion
isMultiSelect
isWide
:answers="listCardCheckBoxHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_check_card"
groupName="horizontal_check-card"
questionText="List Card Horizontal as checkbox"
/>
<buttonQuestion
isMultiSelect
isWide
:answers="listCardCheckBoxHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_check_card-st"
groupName="horizontal_check-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isWide
:answers="listRadioHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card"
groupName="horizontal_cradio-card"
questionText="List Card Horizontal as radio"
/>
<buttonQuestion
isWide
:answers="listRadioHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-st"
groupName="horizontal_cradio-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isMultiSelect
isRow
:answers="listCardGroup"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-group"
groupName="horizontal_cradio-card-group"
questionText="In column layout"
/>
<div class="row">
<div class="col">
<buttonQuestion
:answers="checkboxAnswers"
:isMultiSelect="true"
ariaLabelBy="checkbox"
groupName="checkbox-list"
questionText="List Button as checkbox"
/>
<buttonQuestion
:answers="radioAnswers"
ariaLabelBy="radio"
groupName="radio-list"
questionText="List Button as radio"
/>
<buttonQuestion
:answers="horizontalCheckboxAnswers"
buttonType="listButtonHorizontal"
:isMultiSelect="true"
isColumn
ariaLabelBy="checkbox"
groupName="checkbox-list-horizontal"
questionText="List Button Horizontal as checkbox"
/>
<buttonQuestion
:answers="horizontalRadioAnswers"
buttonType="listButtonHorizontal"
isColumn
ariaLabelBy="radio"
groupName="radio-list-horizontal"
questionText="List Button Horizontal as radio"
sizeInRem="1"
/>
<buttonQuestion
:answers="listCardCheckBox"
buttonType="listCard"
isMultiSelect
ariaLabelBy="check_card"
groupName="check-list-card"
questionText="List Card as checkbox"
/>
<buttonQuestion
:answers="listCardRadio"
buttonType="listCard"
ariaLabelBy="radio_card"
groupName="radio-card"
questionText="List Card as radio"
/>
<buttonQuestion
isMultiSelect
isWide
:answers="listCardCheckBoxHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_check_card"
groupName="horizontal_check-card"
questionText="List Card Horizontal as checkbox"
/>
<buttonQuestion
isMultiSelect
isWide
:answers="listCardCheckBoxHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_check_card-st"
groupName="horizontal_check-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isWide
:answers="listRadioHorizontal"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card"
groupName="horizontal_cradio-card"
questionText="List Card Horizontal as radio"
/>
<buttonQuestion
isWide
:answers="listRadioHorizontalSubText"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-st"
groupName="horizontal_cradio-card-st"
questionText="With Subtext"
/>
<buttonQuestion
isMultiSelect
isRow
:answers="listCardGroup"
buttonType="listCard"
ariaLabelBy="horizontal_radio_card-group"
groupName="horizontal_cradio-card-group"
questionText="In column layout"
/>
</div>
</div>
<div class="row my-4">
<div class="col">
<h4 class="m-0 p-2 bg-light rounded">Funnel Footer</h4>
</div>
</div>
<div class="row my-2">
<div class="col">
<funnelFooter/>
</div>
</div>
</div>
</template>
@ -910,6 +949,7 @@ import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-bu
import checkbox from "@/ux-components/checkbox/checkbox";
import buttonQuestion from "@/common-components/button-question/button-question";
import textLink from "@/ux-components/text-link/text-link";
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
export default {
name: "App",
components: {
@ -924,6 +964,7 @@ export default {
funnelHeader,
buttonQuestion,
textLink,
funnelFooter
},
data() {
return {
@ -946,7 +987,7 @@ export default {
Text: "List Card CB",
SubText: "checkbox",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listCardRadio: [
@ -955,7 +996,7 @@ export default {
Text: "List Card R",
SubText: "Radio",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listCardCheckBoxHorizontalSubText: [
@ -964,7 +1005,7 @@ export default {
Text: "List Card CBHst",
SubText: "With Subtext",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listCardCheckBoxHorizontal: [
@ -972,7 +1013,7 @@ export default {
Name: "List-Card-CBH",
Text: "List Card CBH",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listRadioHorizontalSubText: [
@ -981,7 +1022,7 @@ export default {
Text: "List Card RHst",
SubText: "With Subtext",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listRadioHorizontal: [
@ -989,7 +1030,7 @@ export default {
Name: "List-Card-RH",
Text: "List Card RH",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
listCardGroup: [
@ -998,20 +1039,20 @@ export default {
Text: "Side Window",
SubText: "With Subtext",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
{
Name: "Side-Window-2",
Text: "Side Window",
SubText: "With Subtext that is more than one line",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
{
Name: "Side-Window-3",
Text: "Side Window",
ImageId: "53343ce4-5b6a-46aa-a80a-f948c1723955",
AnswerImageUrl: "side-window-damage-right-all.svg",
AnswerImageUrl: "https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3",
},
],
};

View file

@ -13,6 +13,8 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -26,6 +28,8 @@ jest.mock("@/helpers/cms-content-helper", () => ({
// Mock Store
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
carId: "C00000000",
@ -138,6 +142,28 @@ describe("vehicle-damage.vue", () => {
});
});
describe("vehicle-damage.vue", () => {
test("Call invalidation, ResetPartsAndDeps should be called", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
vehicleDamage.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-damage" } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.resetDependentState();
//Assert
expect(store.dispatch).toBeCalledWith(storeActions.RESET_PARTS_AND_DEPS)
});
});
function setupMocks({
pageHeaderWidgetHeaderText = {},
mountOptionsMockData = {},

View file

@ -20,9 +20,10 @@ import damageLocationQuestion from"@/layouts/vehicle-damage/damage-location-ques
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions";
export default {
name: "vehicle-damage",
@ -81,6 +82,10 @@ export default {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
},
resetDependentState() {
// Invokes
store.dispatch(storeActions.RESET_PARTS_AND_DEPS);
},
},
components: {
funnelHeader,

View file

@ -8,6 +8,7 @@
textPosition="text-start"
:loaderEnabled="true"
v-model="modelValue"
isRequired=true
/>
</template>

View file

@ -1,15 +1,23 @@
import { shallowMount, flushPromises } from "@vue/test-utils";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from "vue";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
// Components
import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue";
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";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from "vue";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import store from "@/store";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
year: 2019,
@ -27,9 +35,6 @@ jest.mock("@/helpers/layout-helper.js", () => ({
settleAllPromises: jest.fn(),
}));
beforeEach(() => {
jest.resetModules();
});
describe("vehicle-make.vue", () => {
test("Make question component is initized with api data", async (done) => {
@ -190,13 +195,40 @@ describe("vehicle-make.vue", () => {
);
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
await nextTick();
//Assert
expect(arePagePrerequisitesValid).toBe(true);
});
});
describe("vehicle-make.vue", () => {
test("Year set, call invalidation, model, style, carId, category should be null", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
vehicleMake.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-make" } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.resetDependentState();
//Assert
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_MODEL, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_DAMAGE_AND_DEPS)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_AND_DEPS)
});
});
function setupMocks({
vehicleMakeQuestionCmsContent = {},
makeQuestionInitialData = {},

View file

@ -25,7 +25,10 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
export default {
name: "vehicle-make",
data() {
@ -75,17 +78,31 @@ export default {
methods: {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK,
this.$route
);
},
arePagePrerequisitesValid() {
return store.getters.vehicle.year !== null;
},
resetDependentState() {
// Set
store.commit(storeMutations.UPDATE_MODEL, null);
store.commit(storeMutations.UPDATE_STYLE, null);
store.commit(storeMutations.UPDATE_CAR_ID, null);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
// Invokes
store.dispatch(storeActions.RESET_DAMAGE_AND_DEPS);
store.dispatch(storeActions.RESET_REGISTRATION_AND_DEPS);
},
},
watch: {
selectedMake(make) {
this.$store.commit(this.storeMutations.UPDATE_MAKE, make);
this.$router.navigate(
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_MAKE,
this.$route
);

View file

@ -8,6 +8,7 @@
textPosition="text-start"
:loaderEnabled="true"
v-model="modelValue"
isRequired=true
/>
</template>

View file

@ -7,10 +7,13 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he
// Supporting files
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { shallowMount, flushPromises } from "@vue/test-utils";
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
@ -24,6 +27,8 @@ jest.mock("@/helpers/cms-content-helper", () => ({
// Mock Store
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
getters: {
vehicle: {
make: "Acura",
@ -182,6 +187,33 @@ describe("vehicle-model.vue", () => {
});
});
describe("vehicle-model.vue", () => {
test("Year set, call invalidation, style, carId, category should be null", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
vehicleModel.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-model" } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.resetDependentState();
//Assert
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_DAMAGE_AND_DEPS)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_AND_DEPS)
});
});
function setupMocks({
buttonQuestionContent = {},
modelQuestionInitialData = {},

View file

@ -24,6 +24,8 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store";
@ -77,17 +79,30 @@ export default {
methods: {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK,
this.$route
);
},
arePagePrerequisitesValid() {
return store.getters.vehicle.make !== null;
},
resetDependentState() {
// Set
store.commit(storeMutations.UPDATE_STYLE, null);
store.commit(storeMutations.UPDATE_CAR_ID, null);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
// Invokes
store.dispatch(storeActions.RESET_DAMAGE_AND_DEPS);
store.dispatch(storeActions.RESET_REGISTRATION_AND_DEPS);
},
},
watch: {
selectedModel(model) {
this.$store.commit(this.storeMutations.UPDATE_MODEL, model);
this.$router.navigate(
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_MODEL,
this.$route
);

View file

@ -8,6 +8,7 @@
textPosition="text-start"
:loaderEnabled="true"
v-model="modelValue"
isRequired=true
/>
</template>

View file

@ -25,6 +25,7 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
export default {
@ -77,7 +78,10 @@ export default {
methods: {
backButtonAction() {
// route to move backwards
this.$router.navigate(this.navigationScenarios.CLICKED_BACK, this.$route);
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK,
this.$route
);
},
setVehicle() {
return this.dispatchNonBlockingStoreAction(
@ -93,13 +97,18 @@ export default {
arePagePrerequisitesValid() {
return store.getters.vehicle.model !== null;
},
resetDependentState() {
// Invokes
store.dispatch(storeActions.RESET_DAMAGE_AND_DEPS);
store.dispatch(storeActions.RESET_REGISTRATION_AND_DEPS);
},
},
watch: {
selectedStyle(style) {
this.$store.commit(this.storeMutations.UPDATE_STYLE, style);
this.setVehicle().then(() => {
this.$router.navigate(
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_STYLE,
this.$route
);

View file

@ -1,18 +1,35 @@
import { shallowMount, flushPromises } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from "vue";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import vehicleYear from "@/layouts/vehicle-year/vehicle-year.vue";
import yearQuestion from "@/layouts/vehicle-year/year-question/year-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";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { nextTick } from "vue";
import store from "@/store";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
}));
// 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-year.vue", () => {
test("Year question component is initized with api data", async (done) => {
//Arrange
@ -123,6 +140,34 @@ describe("vehicle-year.vue", () => {
});
});
describe("vehicle-year.vue", () => {
test("Year set, call invalidation, make, model, style, carId, category should be null", async () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
vehicleYear.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "vehicle-year" } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.resetDependentState();
//Assert
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_MAKE, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_MODEL, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_STYLE, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, null)
expect(store.commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, null)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_DAMAGE_AND_DEPS)
expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_AND_DEPS)
});
});
function setupMocks({
vehicleYearQuestionCmsContent = {},
yearQuestionInitialData = {},
@ -146,7 +191,9 @@ function setupMocks({
yearQuestionInitialData: yearQuestionInitialData,
};
const apiPromise = Promise.resolve(apiResponses);
settleAllPromises.mockImplementation(() => apiPromise);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
//Mock year question methods
yearQuestion.methods = {
@ -162,6 +209,7 @@ function setupMocks({
funnelSubHeader.methods = {
initializeComponent: jest.fn(),
};
const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleYear, mountOptions);
const yearQuestionWrapper = wrapper.findComponent({ name: "yearQuestion" });

View file

@ -20,7 +20,9 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import eventBus from "@/helpers/event-bus/event-bus";
import { storeMutations } from "@/constants/store-mutations";
import { storeActions } from "@/constants/store-actions";
import store from "@/store";
export default {
name: "vehicle-year",
@ -71,8 +73,9 @@ export default {
watch: {
selectedYear(year) {
this.$store.commit(this.storeMutations.UPDATE_YEAR, year);
this.$router.navigate(
this.$router.navigateAfterSave(
this.navigationScenarios.SELECTED_YEAR,
this.$route
);
@ -82,6 +85,19 @@ export default {
arePagePrerequisitesValid() {
return true;
},
resetDependentState() {
// Set
store.commit(storeMutations.UPDATE_MAKE, null);
store.commit(storeMutations.UPDATE_MODEL, null);
store.commit(storeMutations.UPDATE_STYLE, null);
store.commit(storeMutations.UPDATE_CAR_ID, null);
store.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
// Invokes
store.dispatch(storeActions.RESET_DAMAGE_AND_DEPS);
store.dispatch(storeActions.RESET_REGISTRATION_AND_DEPS);
}
},
components: {
yearQuestion,

View file

@ -45,16 +45,16 @@ const routes = [
// If we already have our route, go to it.
if (router.hasRoute(to.query.fmgPage)) {
// Since our route is already in scope, we can grab the component from it and call the arePagePrerequisitesValid function.
const arePagePrerequisitesValid = router
const component = router
.getRoutes()
.filter((x) => x.name === to.query.fmgPage)[0]
.components.default.methods.arePagePrerequisitesValid();
.filter((x) => x.name === to.query.fmgPage)[0].components;
if (!arePagePrerequisitesValid) {
if (!arePagePrerequisitesValid(component)) {
await GoToFunnelStartOn404(next);
}
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
return next({ name: to.query.fmgPage, query: to.query, params: to.params });
}
// Get route info for the given url. Names will have a 1:1 relationship with names in the Cms.
@ -74,7 +74,7 @@ const routes = [
.filter((x) => x.name === routeData[0].name)[0]
.components.default();
if (!nextComponent.default.methods.arePagePrerequisitesValid()) {
if (!arePagePrerequisitesValid(nextComponent)) {
await GoToFunnelStartOn404(next);
}
@ -102,23 +102,36 @@ const router = createRouter({
//---------------------------------------------------------- Router Functions ----------------------------------------------------------
router.navigate = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}) => {
navigate(scenario, currentRoute, false, optionalQuery, optionalParams);
}
router.navigateAfterSave = (scenario, currentRoute, optionalQuery = {}, optionalParams = {}) => {
navigate(scenario, currentRoute, true, optionalQuery, optionalParams);
}
// PRIVATE FUNCTIONS
// Navigate to the next route, depending on the scenario.
router.navigate = (
scenario,
currentRoute,
optionalQuery = {},
optionalParams = {}
) => {
function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery = {}, optionalParams = {}) {
if (!scenario) {
console.error("No scenario provided. Please review the routing table.");
return;
}
// Match our maps up and navigate if we have a destination.
const matchingScenarioMap = router.getNavigationMap(scenario, currentRoute);
const matchingScenarioMap = getNavigationMap(scenario, currentRoute);
if (matchingScenarioMap.destinationFmgPageValue !== undefined) {
// We're always pushing the same path, just changing query strings. Make sure our optional query strings get combined with our fmgPage one.
// If we need to do invalidation
const currentComponent = currentRoute.matched[0].components;
if (invalidateOnSave) {
resetDependentState(currentComponent);
}
router.push({
name: "root",
query: Object.assign(optionalQuery, {
@ -132,7 +145,7 @@ router.navigate = (
};
// Get navigation map depending on the scenario and the current 'page' you're on.
router.getNavigationMap = (scenario, currentRoute) => {
function getNavigationMap(scenario, currentRoute) {
const fmgPageValue = currentRoute.query.fmgPage;
const matchedQueryValue = routingTable
.filter(
@ -197,4 +210,14 @@ async function GoToFunnelStartOn404(next) {
});
}
// Checks arePagePrerequisitesValid on the component passed in.
function arePagePrerequisitesValid(component) {
return component.default.methods.arePagePrerequisitesValid();
}
// Reset dependant state on route change.
function resetDependentState(component) {
return component.default.methods.resetDependentState();
}
export default router;

View file

@ -3,7 +3,228 @@ import { endpoints } from "@/constants/endpoints.js";
import { storeMutations } from "@/constants/store-mutations";
import createPersistedState from "vuex-persistedstate";
import globalMethods from "@/global-methods";
import eventBus from "../helpers/event-bus/event-bus";
// Export State
export const state = {
order: {
vehicle: {
year: null,
make: null,
model: null,
style: null,
carId: null,
category: null,
},
damage: {
isRepair: null,
numberOfChips: null,
glassToReplace: null,
},
},
applicationUser: {
eventBus: [],
},
}
// Export Mutations
export const mutations = {
// VEHICLE MUTATIONS
updateYear(state, 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;
},
updateCarId(state, carId) {
state.order.vehicle.carId = carId;
},
updateVehicleCategory(state, category) {
state.order.vehicle.category = category;
},
// EVENT BUS MUTATIONS
addEventToBus(state, event) {
state.applicationUser.eventBus.push(event);
},
removeEventFromBus(state, eventData) {
const matchedEvent = state.applicationUser.eventBus.find(
({ category, subCategory }) =>
category === eventData.category &&
subCategory === eventData.subCategory
);
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
// If the item exists, remove it.
if (itemIndex > -1) {
state.applicationUser.eventBus.splice(itemIndex, 1);
}
},
// DEPENDENCY MUTATIONS
resetVehicleAndDependencies(state) {
state.order.vehicle.year = null;
state.order.vehicle.make = null;
state.order.vehicle.model = null;
state.order.vehicle.style = null;
state.order.vehicle.carId = null;
state.order.vehicle.category = null;
},
resetDamageAndDependencies(state) {
state.order.damage.isRepair = null;
state.order.damage.numberOfChips = null;
state.order.damage.windshieldGlassToReplace = null;
state.order.damage.driverSideGlassToReplace = null;
state.order.damage.passengerSideGlassToReplace = null;
state.order.damage.rearGlassToReplace = null;
},
resetRegistrationAndDependencies(state) {
},
resetPartsAndDependencies(state) {
}
}
// Export Getters
export const getters = {
vehicle: (state) => state.order.vehicle,
eventBusItem: (state) => (eventCategory, eventSubCategory) => {
const matchedEvent = state.applicationUser.eventBus.find(
({ category, subCategory }) =>
category === eventCategory && subCategory === eventSubCategory
);
return matchedEvent !== undefined ? matchedEvent.eventValue : undefined;
},
eventBus: (state) => state.applicationUser.eventBus,
}
// Export Actions
export const actions = {
// Vehicle API Actions
getVehicleYears(context) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleYears.method,
endpoint: endpoints.GetVehicleYears.url,
payload: {},
});
},
lookupVehicleByYmms(context, { year, make, model, style }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByYmms.method,
endpoint: `${endpoints.LookupVehicleByYmms.url}/${year}/${make}/${model}/${style}`,
payload: {},
});
},
lookupVehicleByVin(context, { vin }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByVin.method,
endpoint: endpoints.LookupVehicleByVin.url,
payload: {
vin: vin, // EX "1J4GW58S4XC541166"
},
});
},
getVehicleMakes(context, { year }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method,
endpoint: `${endpoints.GetVehicleMakes.url}/${year}`,
payload: {},
});
},
getVehicleModels(context, { year, make }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleModels.method,
endpoint: `${endpoints.GetVehicleModels.url}/${year}/${make}`,
payload: {},
});
},
getVehicleStyles(context, { year, make, model }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleStyles.method,
endpoint: `${endpoints.GetVehicleStyles.url}/${year}/${make}/${model}`,
payload: {},
});
},
setVehicle(context, { year, make, model, style }) {
return globalMethods
.callHttpClient({
methods: endpoints.GetVehicle.method,
endpoint: `${endpoints.GetVehicle.url}/${year}/${make}/${model}/${style}`,
payload: {},
})
.then((response) => {
context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId);
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category);
return response;
});
},
getDamageOptions(context, { carId }) {
return globalMethods.callHttpClient({
methods: endpoints.GetDamageOptions.method,
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
payload: {},
});
},
// DEPENDENCY ACTIONS
resetVehicleAndDependencies(context) {
context.commit(storeMutations.RESET_VEHICLE_AND_DEPS);
context.commit(storeMutations.RESET_DAMAGE_AND_DEPS);
context.commit(storeMutations.RESET_REGISTRATION_AND_DEPS);
},
resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_AND_DEPS);
context.commit(storeMutations.RESET_PARTS_AND_DEPS);
},
resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_AND_DEPS);
context.commit(storeMutations.RESET_PARTS_AND_DEPS)
},
resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_PARTS_AND_DEPS);
},
// Content API Actions
getRouteInfo(context, { pageName }) {
return globalMethods.callHttpClient({
method: endpoints.GetRouteInfo.method,
endpoint: endpoints.GetRouteInfo.url,
payload: {
pageName: pageName,
},
});
},
getHomepageName(context) {
return globalMethods.callHttpClient({
method: endpoints.GetHomepageInfo.method,
endpoint: endpoints.GetHomepageInfo.url,
});
},
getPageData(context, { pageName }) {
return globalMethods.callHttpClient({
method: endpoints.GetPageData.method,
endpoint: `${endpoints.GetPageData.url}/${pageName}`,
payload: {},
});
},
getEvoxImage(context, { relativeUrl }) {
return globalMethods.callHttpClient({
method: endpoints.GetPageData.method,
endpoint: relativeUrl,
payload: {},
});
},
}
export default createStore({
plugins: [createPersistedState()],
@ -12,169 +233,8 @@ export default createStore({
// * The CMS can reference the fields by name
// * Return users may have a previous "version" of the model, and we don't want
// them to have a breaking experience, because the model might have changed.
state: {
order: {
vehicle: {
year: null,
make: null,
model: null,
style: null,
carId: null,
category: null,
damage: {
isRepair: null,
numberOfChips: null,
glassToReplace: null,
},
},
},
applicationUser: {
eventBus: [],
},
},
// See IMPORTANT note at top of "state" declaration.
mutations: {
updateYear(state, 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;
},
updateVehicle(state, data) {
state.order.vehicle.carId = data.carId;
state.order.vehicle.category = data.category;
},
addEventToBus(state, event) {
state.applicationUser.eventBus.push(event);
},
removeEventFromBus(state, eventData) {
const matchedEvent = state.applicationUser.eventBus.find(
({ category, subCategory }) =>
category === eventData.category &&
subCategory === eventData.subCategory
);
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
// If the item exists, remove it.
if (itemIndex > -1) {
state.applicationUser.eventBus.splice(itemIndex, 1);
}
},
},
getters: {
vehicle: (state) => state.order.vehicle,
eventBusItem: (state) => (eventCategory, eventSubCategory) => {
const matchedEvent = state.applicationUser.eventBus.find(
({ category, subCategory }) =>
category === eventCategory && subCategory === eventSubCategory
);
return matchedEvent !== undefined ? matchedEvent.eventValue : undefined;
},
eventBus: (state) => state.applicationUser.eventBus,
},
actions: {
// Vehicle API Actions
getVehicleYears(context) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleYears.method,
endpoint: endpoints.GetVehicleYears.url,
payload: {},
});
},
lookupVehicleByYmms(context, { year, make, model, style }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByYmms.method,
endpoint: `${endpoints.LookupVehicleByYmms.url}/${year}/${make}/${model}/${style}`,
payload: {},
});
},
lookupVehicleByVin(context, { vin }) {
return globalMethods.callHttpClient({
method: endpoints.LookupVehicleByVin.method,
endpoint: endpoints.LookupVehicleByVin.url,
payload: {
vin: vin, // EX "1J4GW58S4XC541166"
},
});
},
getVehicleMakes(context, { year }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleMakes.method,
endpoint: `${endpoints.GetVehicleMakes.url}/${year}`,
payload: {},
});
},
getVehicleModels(context, { year, make }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleModels.method,
endpoint: `${endpoints.GetVehicleModels.url}/${year}/${make}`,
payload: {},
});
},
getVehicleStyles(context, { year, make, model }) {
return globalMethods.callHttpClient({
method: endpoints.GetVehicleStyles.method,
endpoint: `${endpoints.GetVehicleStyles.url}/${year}/${make}/${model}`,
payload: {},
});
},
setVehicle(context, { year, make, model, style }) {
return globalMethods
.callHttpClient({
methods: endpoints.GetVehicle.method,
endpoint: `${endpoints.GetVehicle.url}/${year}/${make}/${model}/${style}`,
payload: {},
})
.then((response) => {
context.commit(storeMutations.UPDATE_VEHICLE, response.data);
return response;
});
},
getDamageOptions(context, { carId }) {
return globalMethods.callHttpClient({
methods: endpoints.GetDamageOptions.method,
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
payload: {},
});
},
// Content API Actions
getRouteInfo(context, { pageName }) {
return globalMethods.callHttpClient({
method: endpoints.GetRouteInfo.method,
endpoint: endpoints.GetRouteInfo.url,
payload: {
pageName: pageName,
},
});
},
getHomepageName(context) {
return globalMethods.callHttpClient({
method: endpoints.GetHomepageInfo.method,
endpoint: endpoints.GetHomepageInfo.url,
});
},
getPageData(context, { pageName }) {
return globalMethods.callHttpClient({
method: endpoints.GetPageData.method,
endpoint: `${endpoints.GetPageData.url}/${pageName}`,
payload: {},
});
},
getEvoxImage(context, { relativeUrl }) {
return globalMethods.callMockHttpClient({
method: endpoints.GetPageData.method,
endpoint: relativeUrl,
payload: {},
});
},
},
state,
mutations,
getters,
actions,
});

View file

@ -1,328 +1,483 @@
import store from "./index";
import globalMethods from "@/global-methods";
import { mutations, state, actions, getters } from "@/store";
import { storeMutations } from "@/constants/store-mutations";
describe("Actions", () => {
it("Should return list of years retrieved", async () => {
// Mock global method
globalMethods.callHttpClient = jest.fn();
describe("Mutations", () => {
it("Updates vehicle year in state", () => {
// Arrange
let years = [];
const storeState = state;
// Act
mutations.updateYear(storeState, "2019");
// Assert
expect(storeState.order.vehicle.year).toEqual("2019");
});
it("Updates vehicle make in state", () => {
// Arrange
const storeState = state;
// Act
mutations.updateMake(storeState, "Acura");
// Assert
expect(storeState.order.vehicle.make).toEqual("Acura");
});
it("Updates vehicle model in state", () => {
// Arrange
const storeState = state;
// Act
mutations.updateModel(storeState, "ILX");
// Assert
expect(storeState.order.vehicle.model).toEqual("ILX");
});
it("Updates vehicle style in state", () => {
// Arrange
const storeState = state;
// Act
mutations.updateStyle(storeState, "4 DOOR SEDAN");
// Assert
expect(storeState.order.vehicle.style).toEqual("4 DOOR SEDAN");
});
it("Updates vehicle carId in state", () => {
// Arrange
const storeState = state;
// Act
mutations.updateCarId(storeState, "C0000001");
// Assert
expect(storeState.order.vehicle.carId).toEqual("C0000001");
});
it("Updates vehicle vehicle category in state", () => {
// Arrange
const storeState = state;
// Act
mutations.updateVehicleCategory(storeState, "CAR");
// Assert
expect(storeState.order.vehicle.category).toEqual("CAR");
});
it("Remove item to eventBus in state", () => {
// Arrange
const storeState = state;
const event = { category: "CategoryOne", subCategory: "SubCategoryOne" }
// Act / Assert
mutations.addEventToBus(storeState, event);
expect(storeState.applicationUser.eventBus).toEqual([event]);
// Act / Assert
mutations.removeEventFromBus(storeState, event);
expect(storeState.applicationUser.eventBus).toEqual([]);
});
it("Adds item to eventBus in state", () => {
// Arrange
const storeState = state;
// Act
mutations.addEventToBus(storeState, { EventOne: "ValueOne" });
// Assert
expect(storeState.applicationUser.eventBus).toEqual([{ EventOne: "ValueOne" }]);
});
it("resetVehicleAndDependencies, should set fields to null", () => {
// Arrange
const storeState = state;
mutations.updateYear(storeState, "2019");
mutations.updateMake(storeState, "Acura");
mutations.updateModel(storeState, "ILX");
mutations.updateStyle(storeState, "4 DOOR SEDAN");
mutations.updateCarId(storeState, "C0000001");
mutations.updateVehicleCategory(storeState, "CAR");
// Expect
expect(storeState.order.vehicle.year).toEqual("2019");
expect(storeState.order.vehicle.make).toEqual("Acura");
expect(storeState.order.vehicle.model).toEqual("ILX");
expect(storeState.order.vehicle.style).toEqual("4 DOOR SEDAN");
expect(storeState.order.vehicle.carId).toEqual("C0000001");
expect(storeState.order.vehicle.category).toEqual("CAR");
// Act
mutations.resetVehicleAndDependencies(storeState);
// Expect
expect(storeState.order.vehicle.year).toEqual(null);
expect(storeState.order.vehicle.make).toEqual(null);
expect(storeState.order.vehicle.model).toEqual(null);
expect(storeState.order.vehicle.style).toEqual(null);
expect(storeState.order.vehicle.carId).toEqual(null);
expect(storeState.order.vehicle.category).toEqual(null);
});
it("resetDamageAndDependencies, should set fields to null", () => {
// Arrange
const storeState = state;
storeState.order.damage = {
isRepair: true,
numberOfChips: 2,
windshieldGlassToReplace: "Front",
driverSideGlassToReplace: "Rear",
passengerSideGlassToReplace: "Rear",
rearGlassToReplace: "Slider"
}
// Expect
expect(storeState.order.damage.isRepair).toEqual(true);
expect(storeState.order.damage.numberOfChips).toEqual(2);
expect(storeState.order.damage.windshieldGlassToReplace).toEqual("Front");
expect(storeState.order.damage.driverSideGlassToReplace).toEqual("Rear");
expect(storeState.order.damage.passengerSideGlassToReplace).toEqual("Rear");
expect(storeState.order.damage.rearGlassToReplace).toEqual("Slider");
// Act
mutations.resetDamageAndDependencies(storeState);
// Expect
expect(storeState.order.damage.isRepair).toEqual(null);
expect(storeState.order.damage.numberOfChips).toEqual(null);
expect(storeState.order.damage.windshieldGlassToReplace).toEqual(null);
expect(storeState.order.damage.driverSideGlassToReplace).toEqual(null);
expect(storeState.order.damage.passengerSideGlassToReplace).toEqual(null);
expect(storeState.order.damage.rearGlassToReplace).toEqual(null);
});
});
describe("Actions", () => {
it("getVehicleYears action, should return years array", async () => {
// Arrange
const context = state;
// Act
globalMethods.callHttpClient = jest.fn();
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: [2023, 2022, 2021] });
});
await store.dispatch("getVehicleYears").then((response) => {
years = response.data;
});
// Assert
expect(years[0]).toBe(2023);
const response = await actions.getVehicleYears(context)
expect(response.data).toEqual([2023, 2022, 2021]);
});
it("Should return list of makes retrieved", async () => {
it("lookupVehicleByYmms action, should return car data", async () => {
// Arrange
let makes = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["Baic", "Honda", "Ford"] });
});
await store.dispatch("getVehicleMakes", { year: 2023 }).then((response) => {
makes = response.data;
return Promise.resolve({ data: { carId: "C00000001" } });
});
// Assert
expect(makes[0]).toBe("Baic");
const response = await actions.lookupVehicleByYmms(context, "2019", "Acura", "ILX", "4 DOOR SEDAN")
expect(response.data).toEqual({ carId: "C00000001" });
});
it("Should return list of models retrieved", async () => {
it("lookupVehicleByVin action, should return car data", async () => {
// Arrange
let models = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["BJ40 (MEX)", "Civic", "Accord"] });
return Promise.resolve({ data: { carId: "C00000001" } });
});
await store
.dispatch("getVehicleModels", { year: 2023, make: "Baic" })
.then((response) => {
models = response.data;
});
// Assert
expect(models[0]).toBe("BJ40 (MEX)");
const response = await actions.lookupVehicleByVin(context, "12345678901234567")
expect(response.data).toEqual({ carId: "C00000001" });
});
it("Should return list of styles retrieved", async () => {
it("getVehicleMakes action, should return makes list", async () => {
// Arrange
let styles = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["4 DOOR UTILITY", "2 DOOR"] });
return Promise.resolve({ data: ["Acura", "Honda"] });
});
await store
.dispatch("getVehicleStyles", {
year: 2023,
make: "Baic",
model: "BJ40 (MEX)",
})
.then((response) => {
styles = response.data;
});
// Assert
expect(styles[0]).toBe("4 DOOR UTILITY");
const response = await actions.getVehicleMakes(context, "2019")
expect(response.data).toEqual(["Acura", "Honda"]);
});
it("Should return list of damage options retrieved", async () => {
it("getVehicleModels action, should return models list", async () => {
// Arrange
let damageOptions = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: ["Front Window", "Rear Window"] });
return Promise.resolve({ data: ["ILX", "RDX"] });
});
await store
.dispatch("getDamageOptions", {
carId: "CR00070154",
})
.then((response) => {
damageOptions = response.data;
});
// Assert
expect(damageOptions[0]).toBe("Front Window");
const response = await actions.getVehicleModels(context, "2019", "Acura")
expect(response.data).toEqual(["ILX", "RDX"]);
});
it("Should return data from url retrieved", async () => {
it("getVehicleStyles action, should return models list", async () => {
// Arrange
let routeInfo = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "Route Info Data",
},
});
return Promise.resolve({ data: { style: "4 DOOR SEDAN" } });
});
await store
.dispatch("getRouteInfo", { pageName: "vehicle-year" })
.then((response) => {
routeInfo = response.data.Result;
});
// Assert
expect(routeInfo).toBe("Route Info Data");
const response = await actions.getVehicleStyles(context, "2019", "Acura", "ILX")
expect(response.data).toEqual({ style: "4 DOOR SEDAN" });
});
it("Should return page data from url retrieved", async () => {
it("setVehicle action, should get vehicle data and set carId and vehicle category", async () => {
// Arrange
let pageData = [];
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "Page Info Data",
},
});
return Promise.resolve({ data: { carId: "C00000000", category: "CAR" } });
});
await store
.dispatch("getPageData", { pageName: "vehicle-year" })
.then((response) => {
pageData = response.data.Result;
});
// Assert
expect(pageData).toBe("Page Info Data");
const response = await actions.setVehicle(context, "C00000000")
expect(commit).toBeCalledWith(storeMutations.UPDATE_CAR_ID, "C00000000");
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE_CATEGORY, "CAR");
expect(response.data).toEqual({ carId: "C00000000", category: "CAR" });
});
it("Should return data from url retrieved", async () => {
it("getDamageOptions action", async () => {
// Arrange
let returnData = [];
const context = state;
// Act
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "2018 Honda Civic",
},
});
return Promise.resolve({ data: ["Windshield", "DriversFrontDoor"] });
});
await store
.dispatch("lookupVehicleByYmms", {
year: "2018",
make: "Honda",
model: "Civic",
style: "2 Door",
})
.then((response) => {
returnData = response.data.Result;
});
const response = await actions.getDamageOptions(context, "C00000000")
// Assert
expect(returnData).toBe("2018 Honda Civic");
expect(response.data).toEqual(["Windshield", "DriversFrontDoor"]);
});
it("Should return vehicle data from url retrieved", async () => {
it("resetVehicleAndDependencies action", async () => {
// Arrange
let returnData = [];
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetVehicleAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_VEHICLE_AND_DEPS);
expect(commit).toBeCalledWith(storeMutations.RESET_DAMAGE_AND_DEPS);
expect(commit).toBeCalledWith(storeMutations.RESET_REGISTRATION_AND_DEPS);
});
it("resetDamageAndDependencies action", async () => {
// Arrange
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetDamageAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_DAMAGE_AND_DEPS);
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_AND_DEPS);
});
it("resetRegistrationAndDependencies action", async () => {
// Arrange
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetRegistrationAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_REGISTRATION_AND_DEPS);
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_AND_DEPS);
});
it("resetPartsAndDependencies action", async () => {
// Arrange
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetPartsAndDependencies(context)
expect(commit).toBeCalledWith(storeMutations.RESET_PARTS_AND_DEPS);
});
it("getRouteInfo action, returns route info", async () => {
// Arrange
const context = state;
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({
data: {
Result: "2021 Honda Civic",
},
});
return Promise.resolve({ data: { Widget: "Data" } });
});
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",
},
});
const response = await actions.getRouteInfo(context, "vehicle-year")
expect(response.data).toEqual({ Widget: "Data" });
});
it("getHomepageName action, returns homepage name", async () => {
// Arrange
const context = state;
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { Name: "vehicle-year" } });
});
await store
.dispatch("getEvoxImage", { relativeUrl: "evox_image.com" })
.then((response) => {
returnData = response.data.Result;
});
// Assert
expect(returnData).toBe("2008_honda_civic.jpg");
});
});
describe("Mutations", () => {
it("Should update the year property in the store", () => {
// Act
store.commit("updateYear", 2020);
const response = await actions.getHomepageName(context)
expect(response.data).toEqual({ Name: "vehicle-year" });
// Assert
expect(store.state.order.vehicle.year).toBe(2020);
});
it("Should update the make property in the store", () => {
// Act
store.commit("updateMake", "Honda");
it("getPageData action, returns page data", async () => {
// 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");
});
it("Should update the carID and category propertys in the store", () => {
// Arrange
const carData = {
carId: "123abc",
category: "car",
};
// Act
store.commit("updateVehicle", carData);
const context = state;
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { Results: [{ Widget: "Data" }] } });
});
// Act
const response = await actions.getPageData(context, "vehicle-year")
expect(response.data).toEqual({ Results: [{ Widget: "Data" }] });
// Assert
expect(store.state.order.vehicle.carId).toBe("123abc");
expect(store.state.order.vehicle.category).toBe("car");
});
it("Should add event onto bus and update state", () => {
it("getEvoxImage action, returns image url", async () => {
// Arrange
const event = {
category: "TestCategoryOne",
subCategory: "TestSubCategoryOne",
eventValue: "TestEventValueOne",
};
const context = state;
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { imageUrl: "https://test.com" } });
});
// Act
store.commit("addEventToBus", event);
const response = await actions.getEvoxImage(context, { relativeUrl: "https://relativeurl.com" });
//Assert
expect(store.state.applicationUser.eventBus[0].category).toBe(
"TestCategoryOne"
);
expect(store.state.applicationUser.eventBus[0].subCategory).toBe(
"TestSubCategoryOne"
);
expect(store.state.applicationUser.eventBus[0].eventValue).toBe(
"TestEventValueOne"
);
expect(response.data).toEqual({ imageUrl: "https://test.com" });
});
});
describe("Getters", () => {
it("Should validate vehicle getter", () => {
it("Vehicle getter, should return vehicle data", () => {
// Arrange
const vehicle = store.getters.vehicle;
// Assert
expect(typeof vehicle).toBe("object");
});
it("Should get item from bus via getter", () => {
// Arrange
const event = {
category: "TestCategoryOne",
subCategory: "TestSubCategoryOne",
eventValue: "TestEventValueOne",
};
const storeState = state;
// Act
store.commit("addEventToBus", event);
mutations.updateYear(storeState, "2019");
mutations.updateMake(storeState, "Acura");
mutations.updateModel(storeState, "ILX");
// Assert
const returnedEventValue = store.getters.eventBusItem(
event.category,
event.subCategory
);
expect(returnedEventValue).toBe("TestEventValueOne");
expect(getters.vehicle(storeState).year).toEqual("2019");
expect(getters.vehicle(storeState).make).toEqual("Acura");
expect(getters.vehicle(storeState).model).toEqual("ILX");
});
it("Should get eventbus from getter, should have length > 0", () => {
it("Get event bus item by event category and eventSubCategory", () => {
// Arrange
const event = {
category: "TestCategoryOne",
subCategory: "TestSubCategoryOne",
eventValue: "TestEventValueOne",
};
const storeState = state;
const event = { category: "CategoryOne", subCategory: "SubCategoryOne", eventValue: "EventValueOne" };
// Act
store.commit("addEventToBus", event);
mutations.addEventToBus(storeState, event);
// Assert
//expect(storeState.applicationUser.eventBus).toEqual([event]);
expect(getters.eventBusItem(storeState)(event.category, event.subCategory)).toEqual(event.eventValue);
//Assert
expect(store.getters.eventBus.length).toBeGreaterThan(0);
});
});
it("Get event bus", () => {
// Arrange
const storeState = state;
storeState.applicationUser.eventBus = [];
const event = { category: "CategoryOne", subCategory: "SubCategoryOne", eventValue: "EventValueOne" };
// Act
mutations.addEventToBus(storeState, event);
// Assert
expect(getters.eventBus(storeState)).toEqual([event]);
});
});

View file

@ -1,11 +1,5 @@
<template>
<button
:disabled="isDisabled"
:aria-disabled="isDisabled"
class="btn d-flex align-items-center py-3 px-4"
:class="isPrimary ? 'btn-primary' : 'btn-secondary'"
@click="clicked()"
>
<button :disabled="isDisabled" :aria-disabled="isDisabled" class="btn d-flex align-items-center py-3 px-4" :class="[isPrimary ? 'btn-primary' : 'btn-secondary',isFloat ? 'float-end' : '']" @click="clicked()">
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
@ -27,6 +21,7 @@ export default {
loaderColor: String,
loaderPosition: String,
sizeInRem: [Number, String],
isFloat: Boolean
},
data() {
return {
@ -55,6 +50,7 @@ export default {
border-radius: $border-radius-lg;
color: $white;
transition: all 150ms linear;
white-space: nowrap;
&:hover {
background: linear-gradient(
270deg,

View file

@ -142,6 +142,7 @@ export default {
background: $white;
transition: all 150ms linear;
border: 1px solid $gray-500;
border-radius: 0;
width: 100%;
&:hover {
box-shadow: 0 0 0 4px $blue-100;

View file

@ -1,27 +1,8 @@
<template>
<!-- Navigation Text Link -->
<a
v-if="navigation"
@click="handleClick"
class="navigation-link"
:href="href"
>{{ text }}</a
>
<!-- Footer Text Link -->
<a
v-else-if="footer"
@click="handleClick"
class="footer-link"
:href="href"
target="_blank"
>{{ text }}</a
>
<!-- Small Text Link -->
<a v-else-if="textSmall" @click="handleClick" class="small" :href="href">{{
text
}}</a>
<!-- Default Text Link -->
<a v-else @click="handleClick" :href="href">{{ text }}</a>
<a v-if="navigation" @click="handleClick" class="navigation-link" :href="href">{{text}}</a>
<a v-else-if="footer" @click="handleClick" class="footer-link" :href="href" target="_blank">{{text}}</a>
<a v-else-if="textSmall" @click="handleClick" class="small" :href="href">{{text}}</a>
<a v-else @click="handleClick" :href="href">{{text}}</a>
</template>
<script>
@ -66,9 +47,12 @@ a {
color: $black;
border-bottom: 1px solid $black;
line-height: 26px;
display: inline-flex;
text-transform: capitalize;
white-space: nowrap;
}
&.footer-link {
color: $gray-500;
color: $gray-600;
text-decoration: none;
border-bottom: 1px solid transparent;
line-height: 20px;