CSR-747 Fix merge conflict

This commit is contained in:
Katie 2022-11-30 10:52:20 -05:00
commit bd53177d53
54 changed files with 4019 additions and 695 deletions

View file

@ -13,14 +13,9 @@ module.exports = {
"!src/router/**/*.js", "!src/router/**/*.js",
"!src/helpers/unit-test-helper.js", "!src/helpers/unit-test-helper.js",
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue", "!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
"!src/layouts/molding-questions/**/*.vue",
"!src/layouts/capability-questions/**/*.vue",
"!src/layouts/part-questions/**/*.vue",
"!src/layouts/reveal/**/*.vue", "!src/layouts/reveal/**/*.vue",
"!src/layouts/quote/**/*.vue",
"!src/ux-components/text-link/**/*.vue", "!src/ux-components/text-link/**/*.vue",
"!src/common-components/question-chain/**/*.vue", "!src/common-components/date-picker/**/*.vue",
"!src/layouts/quote/**/*.vue", // Temporary
"!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing "!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing
"!src/common-components/funnel-header/menu-modal/**/*.vue", "!src/common-components/funnel-header/menu-modal/**/*.vue",
// END // END

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 30 14" viewBox="0 0 30 14">
<path d="M7.4 12.8h6.8l3.1-11.6H7.4C4.2 1.2 1.6 3.8 1.6 7s2.6 5.8 5.8 5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#fff"/>
<path d="M22.6 0H7.4c-3.9 0-7 3.1-7 7s3.1 7 7 7h15.2c3.9 0 7-3.1 7-7s-3.2-7-7-7zm-21 7c0-3.2 2.6-5.8 5.8-5.8h9.9l-3.1 11.6H7.4c-3.2 0-5.8-2.6-5.8-5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#06f"/>
<path d="M24.6 4c.2.2.2.6 0 .8L22.5 7l2.2 2.2c.2.2.2.6 0 .8-.2.2-.6.2-.8 0l-2.2-2.2-2.2 2.2c-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8L20.8 7l-2.2-2.2c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0l2.2 2.2L23.8 4c.2-.2.6-.2.8 0z" style="fill:#fff"/>
<path d="M12.7 4.1c.2.2.3.6.1.8L8.6 9.8c-.1.1-.2.2-.3.2-.2.1-.5.1-.7-.1L5.4 7.7c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0L8 8.6l3.8-4.5c.2-.2.6-.2.9 0z" style="fill:#06f"/>
</svg>

After

Width:  |  Height:  |  Size: 865 B

View file

@ -1,4 +1,4 @@
import { shallowMount } from "@vue/test-utils"; import { shallowMount, mount } from "@vue/test-utils";
import buttonQuestion from "@/common-components/button-question/button-question"; import buttonQuestion from "@/common-components/button-question/button-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
@ -115,6 +115,38 @@ describe("buttonQuestion.vue", () => {
"2020", "2020",
]); ]);
}); });
test("should accept an imported component for buttonTypeObject and successfully add it to components", async () => {
// Arrange
const mockComponent = {
name: "mockComponent",
methods: {
mockComponentMethod() {
return "mockComponentMethod return value";
},
},
};
const wrapper = await shallowMount(
buttonQuestion,
setupMocks({
propsData: {
buttonTypeString: "mockComponent",
buttonTypeObject: mockComponent,
},
})
);
// Act
const componentList = wrapper.vm.$options.components;
const componentExists = !!componentList.mockComponent;
const componentNameMatches = componentList.mockComponent.name === mockComponent.name;
const componentMethodMatches =
componentList.mockComponent.methods.mockComponentMethod() ===
mockComponent.methods.mockComponentMethod();
// Assert
expect(componentExists && componentNameMatches && componentMethodMatches).toBe(true);
});
}); });
describe("buttonsInfo", () => { describe("buttonsInfo", () => {

View file

@ -2,7 +2,7 @@ import { mount } from "@vue/test-utils";
import funnelFooter from "./funnel-footer"; import funnelFooter from "./funnel-footer";
describe("funnel-footer.vue", () => { describe("funnel-footer.vue", () => {
it("Should emit ForwardClicked on button click", async () => { test("Should emit ForwardClicked on button click", async () => {
// Act // Act
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin], mixins: [mockMixin],
@ -12,7 +12,7 @@ describe("funnel-footer.vue", () => {
expect(wrapper.emitted()["ForwardClicked"][0]).toHaveBeenCalled; expect(wrapper.emitted()["ForwardClicked"][0]).toHaveBeenCalled;
}); });
it("Should emit BackClicked on link click", async () => { test("Should emit BackClicked on link click", async () => {
// Act // Act
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin], mixins: [mockMixin],
@ -22,7 +22,7 @@ describe("funnel-footer.vue", () => {
expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled; expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled;
}); });
it("Should change button text when update button text is called", async () => { test("Should change button text when update button text is called", async () => {
// Act // Act
const wrapper = mount(funnelFooter, { const wrapper = mount(funnelFooter, {
mixins: [mockMixin], mixins: [mockMixin],
@ -32,6 +32,23 @@ describe("funnel-footer.vue", () => {
// Assert // Assert
expect(wrapper.componentVM.customButtontext).toBe("newText"); expect(wrapper.componentVM.customButtontext).toBe("newText");
}); });
test("should run removeLoader fn on buttonMain and return false for onkeydown fn", async () => {
// Arrange
const wrapper = mount(funnelFooter, {
mixins: [mockMixin],
});
// Act
wrapper.vm.$refs.buttonMain.removeLoader = jest.fn();
wrapper.vm.removeLoader();
const spy = jest.spyOn(document, "onkeydown");
document.onkeydown();
// Assert
expect(wrapper.vm.$refs.buttonMain.removeLoader).toHaveBeenCalled();
expect(spy).toReturnWith(true);
});
}); });
const mockMixin = { const mockMixin = {

View file

@ -44,16 +44,28 @@
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Terms of use" text="Terms of use"
href="https://www.safelite.com/terms-of-use" href="//www.safelite.com/terms-of-use"
target="_blank" /> target="_blank" />
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Privacy policy" text="Your privacy choices"
href="https://www.safelite.com/safelite-group-privacy-policy" href="//www.safelite.com/privacy-center"
target="_blank">
<template v-slot:after-text>
<img
class="ccpa-icon"
src="~@/assets/img/icons/ccpa-icon.svg"
alt="Your privacy choices" />
</template>
</textLink>
<textLink
linkType="navigation"
text="Warranty"
href="//www.safelite.com/national-lifetime-warranty"
target="_blank" /> target="_blank" />
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Do not sell my information" text="Notice at collection"
href="https://privacyportal-cdn.onetrust.com/dsarwebform/d3b95a93-e22e-4d4d-a806-482052406557/9e371601-eae3-4338-9430-b90b9036022b.html" href="https://privacyportal-cdn.onetrust.com/dsarwebform/d3b95a93-e22e-4d4d-a806-482052406557/9e371601-eae3-4338-9430-b90b9036022b.html"
target="_blank" /> target="_blank" />
</div> </div>
@ -147,6 +159,11 @@ export default {
overflow-y: visible; overflow-y: visible;
.modal-body { .modal-body {
padding: 2rem; padding: 2rem;
.ccpa-icon {
width: 2.0625rem;
height: 1rem;
margin-left: 0.5rem;
}
} }
.modal-fullscreen { .modal-fullscreen {
width: 100vw; width: 100vw;

View file

@ -0,0 +1,253 @@
// Components
import questionsPageLayout from "@/common-components/layouts/questions-page-layout/questions-page-layout";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import baseMixin from "../../../mixins/base-mixin";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
// 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(),
}));
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
describe("questionsPageLayout.vue", () => {
describe("method showThisQuestionChain...", () => {
test("Should return true if index prop and passed index match", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({ index: 0 });
const testGlassPiece = {
questions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
answers: [],
},
],
};
const testIndex = 0;
// Act
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
//Assert
expect(result).toBe(true);
wrapper.unmount();
});
test("Should return true if answerResult exists", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({ index: 0 });
const testGlassPiece = {
questions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
answers: [],
},
],
answerData: {
answerResult: "test",
},
};
const testIndex = 1;
// Act
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
//Assert
expect(result).toBe(true);
wrapper.unmount();
});
test("Should return false if indexes don't match and answerResult is missing", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({ index: 0 });
const testGlassPiece = {
questions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
answers: [],
},
],
};
const testIndex = 1;
// Act
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
//Assert
expect(result).toBe(false);
wrapper.unmount();
});
test("Should return false if no questions", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({ index: 0 });
const testGlassPiece = {};
const testIndex = 0;
// Act
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
//Assert
expect(result).toBe(false);
wrapper.unmount();
});
test("Should return false if suppressed", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({ index: 0 });
const testGlassPiece = {
isSuppressedPart: true,
questions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
answers: [],
},
],
answerData: {
answerResult: "test",
},
};
const testIndex = 0;
// Act
const result = wrapper.vm.showThisQuestionChain(testGlassPiece, testIndex);
//Assert
expect(result).toBe(false);
wrapper.unmount();
});
});
describe("method handleForwardButtonAction...", () => {
test("Should emit forwardButtonAction", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({});
// Act
wrapper.vm.handleForwardButtonAction();
//Assert
expect(wrapper.emitted()).toHaveProperty("forwardButtonAction");
wrapper.unmount();
});
});
describe("method handleBackButtonAction...", () => {
test("Should emit backButtonAction", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setProps({});
// Act
wrapper.vm.handleBackButtonAction();
//Assert
expect(wrapper.emitted()).toHaveProperty("back-click");
wrapper.unmount();
});
});
});
function setupMocks() {
const baseStoreGettersPageData = () => {
return {
partsOrQuestions: [
{
parts: null,
partQuestions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
answers: [
{
answerResult: "",
answerText: "Yes",
nextQuestionSequence: 2,
},
{
answerResult: "",
answerText: "No",
nextQuestionSequence: 3,
},
],
},
],
glassLocation: "Windshield",
glassName: "Single",
answerKey: "Windshield-Single",
answerData: null,
},
],
};
};
const baseStoreGettersDamage = () => {
return {
partsQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
},
],
};
};
const mountOptions = getMountOptions({
mixins: [baseMixin, vehicleQuestionsMixin],
});
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(questionsPageLayout, mountOptions);
return { wrapper };
}

View file

@ -27,7 +27,7 @@
ref="funnelFooter" ref="funnelFooter"
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
:isForwardActionDisabled="!isMetaValid" :isForwardActionDisabled="!isMetaValid"
@back-clicked="backButtonAction" @back-clicked="handleBackButtonClicked"
@ForwardClicked="handleForwardButtonAction" /> @ForwardClicked="handleForwardButtonAction" />
</div> </div>
</div> </div>
@ -51,7 +51,7 @@ export default {
alertFewMoreQuestionsCopy: String, alertFewMoreQuestionsCopy: String,
questionsData: Array, questionsData: Array,
validationRules: String, validationRules: String,
modelValue: Array, modelValue: Object,
index: Number, index: Number,
}, },
computed: { computed: {
@ -66,16 +66,17 @@ export default {
}, },
methods: { methods: {
showThisQuestionChain(glass, i) { showThisQuestionChain(glass, i) {
// return false if no questions or if suppressed
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) { if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
return false; return false;
} // return false if no questions or if suppressed }
return this.index === i || glass.answerData?.answerResult?.length > 0; return this.index === i || glass.answerData?.answerResult?.length > 0;
}, },
handleForwardButtonAction() { handleForwardButtonAction() {
this.$emit("forwardButtonAction"); this.$emit("forwardButtonAction");
}, },
handleBackButtonAction() { handleBackButtonAction() {
this.$emit("backButtonAction"); this.$emit("back-click");
}, },
showLoadingModal() { showLoadingModal() {
this.$refs.loadingModal.showModal(); this.$refs.loadingModal.showModal();

View file

@ -6,6 +6,10 @@ describe("modal.vue", () => {
// Act // Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin], mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"])); expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
}); });
@ -14,6 +18,10 @@ describe("modal.vue", () => {
// Act // Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin], mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"])); expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
}); });
@ -22,6 +30,10 @@ describe("modal.vue", () => {
// Act // Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin], mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"])); expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
}); });
@ -30,6 +42,10 @@ describe("modal.vue", () => {
// Act // Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin], mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"])); expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
}); });

View file

@ -16,7 +16,7 @@
aria-label="Close"></button> aria-label="Close"></button>
</div> </div>
<div class="modal-body ps-4 pe-4 pt-5 pb-4"> <div class="modal-body ps-4 pe-4 pt-5 pb-4">
<img :src="this.ModalImage" class="w-100 mb-4" alt="" /> <img :src="this.ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
<h5 class="mb-4" v-html="this.ModalHeadline"></h5> <h5 class="mb-4" v-html="this.ModalHeadline"></h5>
<p class="fw-bold mb-2 subheader-text" v-html="this.ModalSubheadertext"></p> <p class="fw-bold mb-2 subheader-text" v-html="this.ModalSubheadertext"></p>
<p class="mb-0" v-html="this.ModalBodyText"></p> <p class="mb-0" v-html="this.ModalBodyText"></p>
@ -25,7 +25,7 @@
<buttonMain <buttonMain
class="w-100" class="w-100"
ref="buttonMain" ref="buttonMain"
isPrimary suppressLoader
:buttonText="ModalCloseButtonText" :buttonText="ModalCloseButtonText"
@click-event="buttonClick" @click-event="buttonClick"
data-bs-dismiss="modal" /> data-bs-dismiss="modal" />
@ -60,6 +60,12 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "FooterText"); return this.getCmsContent(this.cmsWidgetName, "FooterText");
}, },
}, },
mounted() {
const modal = document.querySelector("#" + this.cmsWidgetName);
modal.addEventListener("hidden.bs.modal", (event) => {
this.$refs.buttonMain.resetButtonStyle();
});
},
components: { components: {
buttonMain, buttonMain,
}, },

View file

@ -0,0 +1,322 @@
import { shallowMount } from "@vue/test-utils";
import questionChain from "@/common-components/question-chain/question-chain.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
describe("Question Chain component", () => {
describe("on create...", () => {
test("Should populate questions data array", async () => {
//Arrange
const { wrapper } = setupMocks({});
const expectedFirstQuestionAnswer = {
answerResult: "DB09410",
buttonLabel: "Yes",
nextQuestionSequence: null,
questionSequence: 1,
questionType: "answer",
value: "1|answer|DB09410|Yes",
};
//Act
await nextTick();
//Assert
expect(wrapper.vm.questions[0].answers[0]).toMatchObject(expectedFirstQuestionAnswer);
});
test("Should not include suppressed questions on questions data array", async () => {
//Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
suppressThisQuestion: true,
},
],
});
//Act
await nextTick();
//Assert
expect(wrapper.vm.questions[0]).toBeFalsy();
});
});
describe("method handleAnswer...", () => {
test("should run getQuestionChainAnswerIfComplete", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "aReturnedAnswer";
wrapper.vm.getQuestionChainAnswerIfComplete = jest.fn();
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.vm.getQuestionChainAnswerIfComplete).toBeCalled();
});
test("should emit update:modelValue if returnedAnswer contains 'answer'", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "1|answer|DB10840|No";
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
expect(wrapper.emitted()["update:modelValue"][0][0]).toMatchObject({
answerResult: "DB10840",
});
});
test("should NOT emit update:modelValue if no returnedAnswer", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
//Act
wrapper.vm.handleAnswer(testQuestion);
//Assert
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
});
test("should NOT emit update:modelValue if returnedAnswer contains 'nextQuestion'", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
});
});
describe("method getQuestionChainAnswerIfComplete...", () => {
test("should return false if no returned answer", () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete();
//Assert
expect(result).toBeFalsy();
});
test("should set the answerSelected to match the answered one", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
});
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(wrapper.vm.questions[0].answerSelected).toBe("1|answer|DB10840|No");
});
test("should remove answers of questions after the answered one", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
{
questionSequence: 2,
questionText: "Question 2",
answers: [
{
answerResult: "2-yes",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "2-no",
answerText: "No",
nextQuestionSequence: null,
},
],
answerSelected: "previously selected answer",
},
],
});
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined;
});
test("should return false if returnedAnswer is a nextQuestion (not a final answer)", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(result).toBeFalsy();
});
test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
await nextTick();
//Assert
expect(Element.prototype.scrollIntoView).toHaveBeenCalled();
});
test("should return answer object if returnedAnswer is a final matching answer", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questions: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
index: 0,
});
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(result).toMatchObject({
answerResult: "DB10840",
answeredQuestions: [
{ questionNum: 1, questionText: "Question here?", selectedAnswerText: "No" },
{
questionNum: 1,
questionText: "Does only your center sliding piece need to be replaced?",
selectedAnswerText: "No",
},
],
index: 0,
});
});
});
});
function setupMocks({
questionDataProp = [
{
questionSequence: 1,
questionText: "Does only your center sliding piece need to be replaced?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
}) {
Element.prototype.scrollIntoView = jest.fn();
const mountOptions = getMountOptions({});
mountOptions.propsData = {
questionData: questionDataProp,
};
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(questionChain, mountOptions);
return { wrapper };
}

View file

@ -1,241 +0,0 @@
import { shallowMount } from "@vue/test-utils";
import questionChain from "@/common-components/question-chain/question-chain.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
jest.mock("@/store",()=>{return{};},{virtual:true});
describe("Question Chain component", () => {
it("Should not emit a modelValue change when setting selectedValue if isNewModelValueComplete is false", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
const localThis = {
$emit: jest.fn(),
getNewModelValue: jest.fn(() => { return false })
}
//Act
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
//Assert
expect(localThis.$emit).not.toBeCalled();
});
it("Should emit a modelValue change when setting selectedValue if isNewModelValueComplete is true", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
const localThis = {
$emit: jest.fn(),
getNewModelValue: jest.fn(() => { return true })
}
//Act
questionChain.computed.selectedValue.set.call(localThis, answerReturned);
//Assert
expect(localThis.$emit).toBeCalledWith("update:modelValue", true);
});
it("should return false if no returned answer is given", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 1;
const answerReturned = null;
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(false);
});
it("should return false if the user's answer on the current question leads to another question", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 1;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(false);
});
it("should return an object with the final answer if the user's answer on the current question is a part number", () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: [] });
wrapper.vm.currentQuestion = 6;
const answerReturned = [wrapper.vm.questions[wrapper.vm.currentQuestion].answers[0].Name.toString()];
//Act
const result = wrapper.vm.getNewModelValue(answerReturned);
//Assert
expect(result).toEqual(
{
answerResult: 'DW02102',
answeredQuestions: [
{
questionText: 'Is your vehicle equipped with heated seats?',
selectedAnswerText: 'Yes'
}
]
}
);
});
});
function setupMocks({
modelValueProp = "",
questionDataProp = {
"glassName": "Single",
"glassLocation": "Windshield",
"parts": null,
"partQuestions": [
{
"questionSequence": 1,
"questionText": "Is your Cherokee the Overland edition which can be identified by having a wood and leather wrapped steering wheel?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": 2,
"answerResult": ""
},
{
"answerText": "No",
"nextQuestionSequence": 3,
"answerResult": ""
}
]
},
{
"questionSequence": 2,
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02270"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02264"
}
]
},
{
"questionSequence": 3,
"questionText": "Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02268"
},
{
"answerText": "No",
"nextQuestionSequence": 4,
"answerResult": ""
}
]
},
{
"questionSequence": 4,
"questionText": "Is your vehicle equipped with automatic climate control which will change the fan speed automatically in order to maintain a set temperature?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": 5,
"answerResult": ""
},
{
"answerText": "No",
"nextQuestionSequence": 6,
"answerResult": ""
}
]
},
{
"questionSequence": 5,
"questionText": "Is your vehicle equipped with heated seats?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02104"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02103"
}
]
},
{
"questionSequence": 6,
"questionText": "Is your vehicle equipped with heated seats?",
"answers": [
{
"answerText": "Yes",
"nextQuestionSequence": null,
"answerResult": "DW02102"
},
{
"answerText": "No",
"nextQuestionSequence": null,
"answerResult": "DW02101"
}
]
}
],
},
methodsToMock = [],
}) {
//Mock store
store.dispatch = jest.fn(() => dataFromStoreApi);
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
const mountOptions = getMountOptions({
store: {
dispatch: store.dispatch,
getters: store.getters,
},
});
//Mock props
const mockMixin = {
methods: {
getCmsContent: jest.fn()
}
}
mountOptions.propsData = {
modelValue: modelValueProp,
questionData: questionDataProp,
};
mountOptions.mixins = [mockMixin];
//Mock methods
methodsToMock.forEach((methodName) => {
questionChain.methods[methodName] = jest.fn();
});
const wrapper = shallowMount(questionChain, mountOptions);
//Mock CMS content
const cmsContent = {
};
return { wrapper, cmsContent };
}

View file

@ -29,7 +29,7 @@ export default {
}; };
}, },
props: { props: {
questionData: Object, questionData: Array,
validationRules: String, validationRules: String,
modelValue: Object, modelValue: Object,
index: Number, index: Number,
@ -84,11 +84,7 @@ export default {
handleAnswer(question, returnedAnswer) { handleAnswer(question, returnedAnswer) {
/* /*
returnedAnswer example format: returnedAnswer example format:
{ "1|answer|DD11132|Yes"
"checkValue": "1|answer|DD11132|Yes",
"value": "1|answer|DD11132|Yes",
"buttonId": "Driver-Front-1-1|answer|DD11132|Yes"
}
*/ */
question.answerSelected = returnedAnswer; question.answerSelected = returnedAnswer;

View file

@ -4,6 +4,7 @@
:for="inputId" :for="inputId"
:aria-label="questionText" :aria-label="questionText"
class="form-label" class="form-label"
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
v-html="labelText"></label> v-html="labelText"></label>
<div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']"> <div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']">
<input <input
@ -22,7 +23,6 @@
:class="[ :class="[
hasIcon ? 'has-icon' : '', hasIcon ? 'has-icon' : '',
iconRight ? 'icon-right' : '', iconRight ? 'icon-right' : '',
questionAlignment === 'center' ? 'text-center' : '',
cornerStyle === 'rounded' ? 'rounded-pill' : '', cornerStyle === 'rounded' ? 'rounded-pill' : '',
]" ]"
:validationRules="validationRules" :validationRules="validationRules"
@ -203,6 +203,7 @@ export default {
border: 1px solid $gray-500; border: 1px solid $gray-500;
border-radius: 0.5rem; border-radius: 0.5rem;
min-height: 3rem; min-height: 3rem;
max-height: 48px;
padding: 12px 16px; padding: 12px 16px;
&::placeholder { &::placeholder {
color: $gray-500; color: $gray-500;

View file

@ -62,6 +62,18 @@ const endpoints = {
url: "/parts/api/v1/parts/parts", url: "/parts/api/v1/parts/parts",
method: "POST", method: "POST",
}, },
GetWipers: {
url: "/parts/api/v1/parts/wipers",
method: "GET",
},
GetRainDefense: {
url: "/parts/api/v1/parts/rain-defense",
method: "GET",
},
GetSupportingItems: {
url: "/parts/api/v1/parts/supporting-items",
method: "POST",
},
GetCapabilityQuestions: { GetCapabilityQuestions: {
url: "/parts/api/v1/parts/capability-questions", url: "/parts/api/v1/parts/capability-questions",
method: "GET", method: "GET",
@ -76,12 +88,16 @@ const endpoints = {
}, },
LoadSession: { LoadSession: {
url: "/order/api/v1/order/load-session", url: "/order/api/v1/order/load-session",
method: "POST", method: "GET",
}, },
ValidateZip: { ValidateZip: {
url: "/location/api/v1/location/zip", url: "/location/api/v1/location/zip",
method: "GET", method: "GET",
}, },
PriceOrderItems: {
url: "/price/api/v1/price/order-items",
method: "POST",
},
LogExperimentExposureIfAssigned: { LogExperimentExposureIfAssigned: {
url: "/experiments/api/v1/experiments/log-exposure", url: "/experiments/api/v1/experiments/log-exposure",
method: "POST", method: "POST",

View file

@ -12,6 +12,7 @@ const storeActions = {
SET_VEHICLE: "setVehicle", SET_VEHICLE: "setVehicle",
GET_DAMAGE_OPTIONS: "getDamageOptions", GET_DAMAGE_OPTIONS: "getDamageOptions",
GET_EVOX_IMAGE: "getEvoxImage", GET_EVOX_IMAGE: "getEvoxImage",
IS_VIN_OPTIONAL_VEHICLE: "isVinOptionalVehicle",
// Lookup Actions // Lookup Actions
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms", LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
@ -20,6 +21,9 @@ const storeActions = {
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress", LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
GET_PARTS: "getParts", GET_PARTS: "getParts",
GET_WIPERS: "getWipers",
GET_RAIN_DEFENSE: "getRainDefense",
GET_SUPPORTING_ITEMS: "getSupportingItems",
GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions", GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions",
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer", GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOLDING_QUESTIONS: "getMoldingQuestions",
@ -27,6 +31,7 @@ const storeActions = {
LOAD_SESSION: "loadSession", LOAD_SESSION: "loadSession",
UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse", UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE: "updateStoreWithSaveSessionResponse",
VALIDATE_ZIP: "validateZip", VALIDATE_ZIP: "validateZip",
PRICE_ORDER_ITEMS: "priceOrderItems",
LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure", LOG_EXPERIMENT_EXPOSURE: "logExperimentExposure",
LOG_PAGE_VIEW: "logPageView", LOG_PAGE_VIEW: "logPageView",
LOG_CUSTOM_EVENT: "logCustomEvent", LOG_CUSTOM_EVENT: "logCustomEvent",
@ -37,7 +42,6 @@ const storeActions = {
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise", RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
// DEPENDENCY MUTATIONS // DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies",
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies", RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
RESET_REGISTRATION_STATE_AND_DEPENDENCIES: "resetRegistrationAndDependencies", RESET_REGISTRATION_STATE_AND_DEPENDENCIES: "resetRegistrationAndDependencies",
RESET_PARTS_STATE_AND_DEPENDENCIES: "resetPartsAndDependencies", RESET_PARTS_STATE_AND_DEPENDENCIES: "resetPartsAndDependencies",
@ -62,6 +66,7 @@ const storeActions = {
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers", SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers", SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
SAVE_IS_INSURANCE: "saveIsInsurance", SAVE_IS_INSURANCE: "saveIsInsurance",
SAVE_QUOTE_PAGE_SELECTIONS: "saveQuotePageSelections",
}; };
export { storeActions }; export { storeActions };

View file

@ -19,7 +19,8 @@ const storeMutations = {
UPDATE_MOLDING_QUESTION_ANSWERS: "updateMoldingQuestionAnswers", UPDATE_MOLDING_QUESTION_ANSWERS: "updateMoldingQuestionAnswers",
UPDATE_CAPABILITY_QUESTION_ANSWERS: "updateCapabilityQuestionAnswers", UPDATE_CAPABILITY_QUESTION_ANSWERS: "updateCapabilityQuestionAnswers",
UPDATE_GLASS_PARTS: "updateGlassParts", UPDATE_GLASS_PARTS: "updateGlassParts",
UPDATE_OTHER_PARTS: "updateOtherParts", UPDATE_VAPS: "updateVaps",
UPDATE_SUPPORTING_ITEMS: "updateSupportingItems",
UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate", UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress", UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
@ -36,14 +37,13 @@ const storeMutations = {
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress", UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
UPDATE_IS_INSURANCE: "updateIsInsurance",
// ORDER MUTATIONS // ORDER MUTATIONS
UPDATE_REFERRAL_NUMBER: "updateReferralNumber", UPDATE_REFERRAL_NUMBER: "updateReferralNumber",
UPDATE_REFERRAL_DATE: "updateReferralDate", UPDATE_REFERRAL_DATE: "updateReferralDate",
UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId", UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId",
UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber", UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber",
UPDATE_EON: "updateEON", UPDATE_EON: "updateEON",
UPDATE_IS_INSURANCE: "updateIsInsurance",
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId", UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
UPDATE_CRM_CUSTOMER_ID: "updateCrmCustomerId", UPDATE_CRM_CUSTOMER_ID: "updateCrmCustomerId",
@ -56,6 +56,8 @@ const storeMutations = {
RESET_DAMAGE_STATE: "resetDamageState", RESET_DAMAGE_STATE: "resetDamageState",
RESET_REGISTRATION_STATE: "resetRegistrationState", RESET_REGISTRATION_STATE: "resetRegistrationState",
RESET_GLASS_PARTS_STATE: "resetGlassPartsState", RESET_GLASS_PARTS_STATE: "resetGlassPartsState",
RESET_SUPPORTING_ITEMS_STATE: "resetSupportingItemsState",
RESET_VAPS_STATE: "resetVapsState",
RESET_STATE: "resetState", RESET_STATE: "resetState",
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise", RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",

View file

@ -21,6 +21,7 @@ export function updateOrCreateFunnelCookie() {
ReferralParentAccountNumber: store.getters.order.accountNumber, ReferralParentAccountNumber: store.getters.order.accountNumber,
HasDelayedClaimRegistration: wasClaimRegistrationDelayed, HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
SuppressConceptFunnel: shouldSuppressConceptFunnel, SuppressConceptFunnel: shouldSuppressConceptFunnel,
SavedSessionId: store.getters.applicationUser.savedSessionId,
}); });
} }

View file

@ -3,6 +3,8 @@ import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
import { fmgPageValues } from "@/router/router-constants/fmgPage-values"; import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
import { storeActions } from "@/constants/store-actions.js";
import { settleAllPromises } from "@/helpers/layout-helper";
import store from "@/store"; import store from "@/store";
import router from "@/router"; import router from "@/router";
@ -78,6 +80,8 @@ async function getLatestPageForRedirection() {
fmgPageValues.CAPABILITY_QUESTIONS fmgPageValues.CAPABILITY_QUESTIONS
); );
const isVinOptionalVehicle = await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE);
if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) { if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
return fmgPageValues.VEHICLE_YEAR; return fmgPageValues.VEHICLE_YEAR;
} else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) { } else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) {
@ -99,7 +103,8 @@ async function getLatestPageForRedirection() {
return fmgPageValues.PART_QUESTIONS; return fmgPageValues.PART_QUESTIONS;
} else if ( } else if (
vinLookupComponent.methods.arePagePrerequisitesValid() && vinLookupComponent.methods.arePagePrerequisitesValid() &&
!store.getters.damage.isRepair !store.getters.damage.isRepair &&
!isVinOptionalVehicle
) { ) {
return fmgPageValues.VIN_LOOKUP; return fmgPageValues.VIN_LOOKUP;
} else { } else {

View file

@ -5,6 +5,7 @@ import {
import * as orderHelper from "@/helpers/heritage-integration/order-helper"; import * as orderHelper from "@/helpers/heritage-integration/order-helper";
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { setupMocksForJsFiles, getMockOrderInfo } from "@/helpers/unit-test-helper.js"; import { setupMocksForJsFiles, getMockOrderInfo } from "@/helpers/unit-test-helper.js";
import { externalUrls } from "@/router/router-constants/externalUrl-values"; import { externalUrls } from "@/router/router-constants/externalUrl-values";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
@ -25,6 +26,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: false, [fmgPageValues.VEHICLE_MAKE]: false,
@ -43,6 +47,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -62,6 +69,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -82,6 +92,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -103,6 +116,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -125,6 +141,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -152,6 +171,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -179,6 +201,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -206,6 +231,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -233,6 +261,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -260,6 +291,9 @@ describe("getPageToRouteExistingOrderTo", () => {
query: {}, query: {},
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Mock out the lazy load calls for all components. // Mock out the lazy load calls for all components.
mockLazyLoadComponentReturnValues({ mockLazyLoadComponentReturnValues({
[fmgPageValues.VEHICLE_MAKE]: true, [fmgPageValues.VEHICLE_MAKE]: true,
@ -289,6 +323,9 @@ describe("getPageToRouteExistingOrderTo", () => {
}, },
}; };
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
// Act // Act
const result = await getPageToRouteExistingOrderTo(toRoute, true); const result = await getPageToRouteExistingOrderTo(toRoute, true);

View file

@ -17,12 +17,8 @@ import { storeMutations } from "@/constants/store-mutations";
export async function loadSessionIfPresent() { export async function loadSessionIfPresent() {
const funnelCookie = getFunnelCookie(); const funnelCookie = getFunnelCookie();
// Do nothing if there is no cookie, correlation id, or referral number. // Do nothing if there is no cookie or session to use for loading.
if ( if (funnelCookie == null || funnelCookie.SavedSessionId == null) {
funnelCookie == null ||
funnelCookie.ReferralCorrelationId == null ||
!funnelCookie.ReferralNumber
) {
return null; return null;
} }
@ -33,15 +29,8 @@ export async function loadSessionIfPresent() {
return null; return null;
} }
// Load referral if there is a cookie, and it doesn't indicate it needs a state reset. // Loads session including referral if there is a cookie, and it doesn't indicate it needs a state reset.
return ( return (await loadSession(funnelCookie.SavedSessionId)).data;
await loadSession(
funnelCookie.ReferralNumber,
funnelCookie.ReferralDate,
funnelCookie.ReferralCorrelationId,
funnelCookie.ReferralParentAccountNumber,
)
).data;
} }
/* /*
@ -72,16 +61,13 @@ export async function saveSession() {
Calls API to load session given the referral number, referralDate, and referralCorrelationId Calls API to load session given the referral number, referralDate, and referralCorrelationId
and returns the response. and returns the response.
*/ */
async function loadSession(referralNumber, referralDate, referralCorrelationId, accountNumber) { async function loadSession(savedSessionId) {
// await the saveSessionPromise in the store to make sure we're loading up to date information // await the saveSessionPromise in the store to make sure we're loading up to date information
await store.getters.applicationUser.saveSessionPromise; await store.getters.applicationUser.saveSessionPromise;
const response = await baseMixin.methods.dispatchStoreAction( const response = await baseMixin.methods.dispatchStoreAction(
storeActions.LOAD_SESSION, storeActions.LOAD_SESSION,
{ {
referralNumber: referralNumber.toString(), savedSessionId: savedSessionId?.toString(),
referralDate: referralDate,
referralCorrelationId: referralCorrelationId,
accountNumber: accountNumber?.toString(),
}, },
false false
); );

View file

@ -24,6 +24,7 @@ describe("loadSessionIfPresent", () => {
ShouldResetState: testShouldResetState, ShouldResetState: testShouldResetState,
ReferralCorrelationId: "xxx", ReferralCorrelationId: "xxx",
ReferralNumber: "12345", ReferralNumber: "12345",
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
}; };
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${JSON.stringify( document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${JSON.stringify(
@ -46,6 +47,7 @@ describe("loadSessionIfPresent", () => {
ShouldResetState: true, ShouldResetState: true,
ReferralCorrelationId: "xxx-xxx-xxx", ReferralCorrelationId: "xxx-xxx-xxx",
ReferralNumber: "12345", ReferralNumber: "12345",
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
}); });
const mockData = { const mockData = {
@ -103,6 +105,7 @@ describe("loadSessionIfPresent", () => {
ReferralNumber: 123456, ReferralNumber: 123456,
ReferralCorrelationId: "yyy-yyy-yyyy", ReferralCorrelationId: "yyy-yyy-yyyy",
ReferralDate: new Date(), ReferralDate: new Date(),
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
}); });
const mockData = { const mockData = {

View file

@ -292,7 +292,7 @@ describe("address-lookup.vue", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
undefined, undefined,
{}, {},
{}, {},
@ -381,7 +381,7 @@ describe("address-lookup.vue", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
undefined, undefined,
{}, {},
{ displayVehicleChangeAlert: true } { displayVehicleChangeAlert: true }

View file

@ -364,7 +364,7 @@ export default {
matchingCars.length === 1 matchingCars.length === 1
) { ) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route, this.$route,
{}, {},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true } { [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }
@ -373,7 +373,7 @@ export default {
await this.navigateForwardWithSingleCarMatch(); await this.navigateForwardWithSingleCarMatch();
} else { } else {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
this.navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, this.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
this.$route, this.$route,
{}, {},
{}, {},

View file

@ -0,0 +1,404 @@
// Components
import capabilityQuestions from "@/layouts/capability-questions/capability-questions";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import baseMixin from "../../mixins/base-mixin";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { nextTick } from "vue";
// 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(),
}));
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
const baseStoreGettersPageData = () => {
return {
partsOrQuestions: [
{
parts: [
{
childPartQuestions: [],
},
],
capabilityQuestions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the optional Lane-Keeping System which tugs on the steering wheel and/or beeps to alert you if you drift too close to the edge of the lane?",
answers: [
{
answerResult1: "DYNAMIC",
answerResult2: "1",
answerText: "Yes",
nextQuestionSequence: null,
answerResult: "DYNAMIC",
},
{
answerResult1: "Unknown",
answerResult2: "0",
answerText: "No",
nextQuestionSequence: null,
answerResult: "Unknown",
},
],
},
],
partQuestions: [],
questions: [],
glassLocation: "Windshield",
glassName: "Single",
answerKey: "Windshield-Single",
answerData: null,
},
],
};
};
const baseStoreGettersDamage = () => {
return {
partsQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
},
],
};
};
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
store.commit = jest.fn();
afterEach(() => {
// reset store after each test
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
});
describe("capabilityQuestions.vue", () => {
describe("method arePagePrerequisitesValid...", () => {
test("Should return true for valid page requisites if pageData exists", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBe(true);
wrapper.unmount();
});
test("Should return false for valid page requisites if partsOrQuestions in pageData is missing", () => {
// Arrange
const { wrapper } = setupMocks({});
store.getters.pageData = jest.fn(() => {
return undefined;
});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
test("Should be at least one item in partsOrQuestions", () => {
// Arrange
store.getters = {
pageData: jest.fn(() => {
return {
partsOrQuestions: [],
};
}),
damage: baseStoreGettersDamage,
};
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
});
describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => {
// Arrange
const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates");
// Act
wrapper.setData({
selectedAnswers: {
"Windshield-Single": {
answerResult: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
index: 0,
},
},
});
await nextTick();
//Assert
expect(spy).toHaveBeenCalled();
wrapper.unmount();
});
});
describe("forwardButtonAction", () => {
test("Should clear out answerData", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
questions: [],
parts: [
{
childPartQuestions: [{}],
},
],
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
// Act
wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
wrapper.unmount();
});
test("Should save to Vuex store", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
questions: [],
parts: [
{
childPartQuestions: [],
},
],
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(spy).toHaveBeenNthCalledWith(
1,
"saveCapabilityQuestionAnswers",
[
{
answeredQuestions: [],
glassLocation: "Windshield",
glassName: "Single",
isSuppressedPart: undefined,
result: "FW04848",
result1: "FW04848",
result2: undefined,
},
],
false
);
wrapper.unmount();
});
test("Should call GET_PART_FROM_CAPABILITY_QUESTION_ANSWER API", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
questions: [],
parts: [
{
childPartQuestions: [],
},
],
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(spy).toHaveBeenNthCalledWith(
2,
"getPartFromCapabilityQuestionAnswer",
"Windshield",
false
);
wrapper.unmount();
});
test("Should trigger navigateForward", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
questions: [],
parts: [
{
childPartQuestions: [],
},
],
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: [],
};
});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
wrapper.unmount();
});
});
});
function setupMocks({
mountOptionsMockData = {
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
},
store: {
getters: store.getters,
commit: store.commit,
},
route: {
query: {
fmgPage: "capability-questions",
},
},
},
}) {
const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, vehicleQuestionsMixin],
});
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(capabilityQuestions, mountOptions);
return { wrapper };
}

View file

@ -6,17 +6,17 @@
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader" :alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy" :alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="questions - required" validationRules="questions-required"
v-model="selectedAnswers" v-model="selectedAnswers"
@forwardButtonAction="forwardButtonAction" @forwardButtonAction="forwardButtonAction"
@backButtonAction="backButtonAction" @back-click="navigateBack"
:index="currentGlassIndex" /> :index="currentGlassIndex" />
</Form> </Form>
</template> </template>
<script> <script>
// Components // Components
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/common-components/layouts/questions-page-layout/questions-page-layout";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -67,38 +67,13 @@ export default {
AlertFewMoreQuestionsCopy() { AlertFewMoreQuestionsCopy() {
return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText"); return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText");
}, },
pageData() { partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS); return this.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
.partsOrQuestions;
}, },
}, },
mounted() { mounted() {
// are there alreadyAnsweredQuestions? this.getInitialQuestionData();
const alreadyAnsweredQuestions = store.getters.damage.capabilityQuestionAnswers;
this.questionsData = this.pageData.partsOrQuestions
.filter((x) => x.capabilityQuestions)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.capabilityQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
// Set up watch for each set of glass questions
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
@ -106,9 +81,46 @@ export default {
fmgPageValues.CAPABILITY_QUESTIONS fmgPageValues.CAPABILITY_QUESTIONS
); );
return ( return (
capabilityQuestionsPageData && Object.keys(capabilityQuestionsPageData).length > 0 // has capabilityQuestions array and has glassName not null
capabilityQuestionsPageData?.partsOrQuestions?.some((part) => part?.glassName) &&
capabilityQuestionsPageData?.partsOrQuestions?.some(
(part) => part?.capabilityQuestions?.length > 0
)
); );
}, },
getInitialQuestionData() {
// get any questions that were already answered
const alreadyAnsweredQuestions = store.getters.damage.capabilityQuestionAnswers;
this.questionsData = this.partsOrQuestionsData
.filter((x) => x.capabilityQuestions)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.capabilityQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(
glass,
index,
alreadyAnsweredQuestions
);
// Set up watch for each set of glass questions
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
},
async forwardButtonAction() { async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => { const questionAnswersArray = this.questionsData.map((glass) => {
// get answerResult2 of returned answer // get answerResult2 of returned answer
@ -139,6 +151,7 @@ export default {
}); });
// save to vuex store as order.damage.capabilityQuestionAnswers (array) // save to vuex store as order.damage.capabilityQuestionAnswers (array)
// used in GET_PART_FROM_CAPABILITY_QUESTION_ANSWER call following this one
await this.dispatchStoreAction( await this.dispatchStoreAction(
this.storeActions.SAVE_CAPABILITY_QUESTION_ANSWERS, this.storeActions.SAVE_CAPABILITY_QUESTION_ANSWERS,
questionAnswersArray, questionAnswersArray,
@ -146,7 +159,7 @@ export default {
); );
// get parts from the capabilityQuestionAnswers // get parts from the capabilityQuestionAnswers
const partsOrQuestions = this.pageData.partsOrQuestions; const partsOrQuestions = this.partsOrQuestionsData;
for (let answer of questionAnswersArray) { for (let answer of questionAnswersArray) {
const partFromCapabilityQuestionAnswer = ( const partFromCapabilityQuestionAnswer = (
await this.dispatchStoreAction( await this.dispatchStoreAction(

View file

@ -61,6 +61,9 @@ describe("estimate.vue", () => {
selectedVinLookupMethod: vinLookupMethodSelections.MANUALVIN, selectedVinLookupMethod: vinLookupMethodSelections.MANUALVIN,
}); });
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
store.commit(storeMutations.UPDATE_MAKE, "acura");
//Act //Act
await wrapper.vm.forwardButtonAction(); await wrapper.vm.forwardButtonAction();
@ -216,8 +219,27 @@ describe("estimate.vue", () => {
}); });
}); });
describe("skipVinLookup", () => {
const skipOptions = [
[true, true, true],
[true, false, true],
[false, true, true],
[false, false, false],
];
test.each(skipOptions)(
"isRepair %s and isVinOptional %s should return %s",
async (isRepair, isVinOptionalVehicle, expectedVinSkip) => {
const { wrapper } = setupMocks({ isVinOptionalVehicle: isVinOptionalVehicle });
store.commit(storeMutations.UPDATE_IS_REPAIR, isRepair);
expect(wrapper.vm.skipVinLookup).toEqual(expectedVinSkip);
}
);
});
function setupMocks({ function setupMocks({
groupName = "estimate", groupName = "estimate",
isVinOptionalVehicle = false,
cmsQuestionText = "Let's get your VIN. Or we can look it up for you!", cmsQuestionText = "Let's get your VIN. Or we can look it up for you!",
cmsAnswers = [ cmsAnswers = [
{ Name: "Provide my VIN manually Most specific to your vehicle" }, { Name: "Provide my VIN manually Most specific to your vehicle" },
@ -249,6 +271,7 @@ function setupMocks({
mountOptions["attachTo"] = document.body; mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(estimate, mountOptions); const wrapper = shallowMount(estimate, mountOptions);
wrapper.vm.isVinOptionalVehicle = isVinOptionalVehicle;
return { wrapper, apiPromise }; return { wrapper, apiPromise };
} }

View file

@ -6,7 +6,7 @@
<vehicleBanner cmsWidgetName="VehicleBannerWidget" /> <vehicleBanner cmsWidgetName="VehicleBannerWidget" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<div class="fade-on-route-transition sub-container make-tall"> <div class="fade-on-route-transition sub-container make-tall">
<div v-if="!isRepair"> <div v-if="!skipVinLookup">
<alert <alert
class="vinLookupMethodHeading" class="vinLookupMethodHeading"
cmsWidgetName="AlertVinLookupQuestion" cmsWidgetName="AlertVinLookupQuestion"
@ -127,12 +127,14 @@ export default {
emailAddress: this.getEmailFromStore(), emailAddress: this.getEmailFromStore(),
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
displayNonServiceableZipAlert: false, displayNonServiceableZipAlert: false,
isVinOptionalVehicle: false,
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
//Call APIs //Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage); const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
//Settle promises and get results //Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
{ {
@ -140,12 +142,16 @@ export default {
promise: cmsContentPromise, promise: cmsContentPromise,
}, },
]; ];
const resultMap = await settleAllPromises(promiseResultMap); const resultMap = await settleAllPromises(promiseResultMap);
const isVinOptionalVehicle = await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE);
next((vm) => { next((vm) => {
vm.isVinOptionalVehicle = isVinOptionalVehicle;
if (resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.includes("|")) { if (resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.includes("|")) {
const forwardTextOption = const forwardTextOption =
resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.split("|"); resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText.split("|");
if (store.getters.damage.isRepair) { if (store.getters.damage.isRepair || vm.isVinOptionalVehicle) {
resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText = resultMap.cmsContent.FunnelFooterWidget.ForwardButtonText =
forwardTextOption[1]; forwardTextOption[1];
} else { } else {
@ -172,9 +178,9 @@ export default {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route); this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
}, },
async forwardButtonAction() { async forwardButtonAction() {
if (this.isRepair) { if (this.skipVinLookup) {
const zipCodeData = await this.getZipCodeData(this.serviceZipCode); const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
//todo: validation
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false); await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailAddress, false);
await this.dispatchStoreAction( await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION, storeActions.SAVE_SERVICE_LOCATION,
@ -200,10 +206,10 @@ export default {
const payment = this.$store.getters.payment; const payment = this.$store.getters.payment;
// TODO KO If coverageStatus is null, they haven't gone to heritage yet... I think // TODO KO If coverageStatus is null, they haven't gone to heritage yet... I think
if (payment.isInsurance && payment.insuranceCoverage.coverageStatus) { if (payment.isInsurance && payment.insuranceCoverage.coverageStatus) {
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal }) navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
} else { } else {
return this.$router.navigateWithSaving( return this.$router.navigateWithSaving(
this.navigationScenarios.HAS_NO_QUESTIONS, this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
this.$route this.$route
); );
} }
@ -249,6 +255,9 @@ export default {
isRepair() { isRepair() {
return store.getters.damage.isRepair; return store.getters.damage.isRepair;
}, },
skipVinLookup() {
return this.isRepair || this.isVinOptionalVehicle;
},
}, },
watch: { watch: {
serviceZipCode() { serviceZipCode() {

View file

@ -0,0 +1,351 @@
// Components
import moldingQuestions from "@/layouts/molding-questions/molding-questions";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import baseMixin from "../../mixins/base-mixin";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { nextTick } from "vue";
// 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(),
}));
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
const baseStoreGettersPageData = () => {
return {
partsOrQuestions: [
{
parts: [
{
childPartQuestions: [
{
questionSequence: 1,
questionText:
"Does the rubber seal around your windshield have a chrome strip running through it?",
answers: [
{
answerResult: "WKT D1106 C",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "WKT D1106 B",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
basePartNumber: "DW01105",
color: "Green Tint, Blue Shade",
requiresRecalibration: false,
recalibrationType: "",
canSafeliteRecalibrate: false,
requiresCapabilityQuestions: false,
childParts: null,
partNumber: "DW01105GBNN",
description: "solar",
partType: "WINDSHIELD",
},
],
partQuestions: [],
glassLocation: "Windshield",
glassName: "Single",
answerKey: "Windshield-Single",
answerData: null,
},
],
};
};
const baseStoreGettersDamage = () => {
return {
partsQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
},
],
};
};
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
store.commit = jest.fn();
afterEach(() => {
// reset store after each test
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
});
describe("moldingQuestions.vue", () => {
describe("method arePagePrerequisitesValid...", () => {
test("Should return true for valid page requisites if pageData exists", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBe(true);
wrapper.unmount();
});
test("Should return false for valid page requisites if partsOrQuestions in pageData is missing", () => {
// Arrange
const { wrapper } = setupMocks({});
store.getters.pageData = jest.fn(() => {
return undefined;
});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
test("Should be at least one item in partsOrQuestions", () => {
// Arrange
store.getters = {
pageData: jest.fn(() => {
return {
partsOrQuestions: [],
};
}),
damage: baseStoreGettersDamage,
};
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
});
describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => {
// Arrange
const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates");
// Act
wrapper.setData({
selectedAnswers: {
"Windshield-Single": {
answerResult: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
index: 0,
},
},
});
await nextTick();
//Assert
expect(spy).toHaveBeenCalled();
wrapper.unmount();
});
});
describe("forwardButtonAction", () => {
test("Should clear out answerData", () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
// Act
wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
wrapper.unmount();
});
test("Should save to Vuex store", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(spy).toHaveBeenNthCalledWith(
1,
"saveMoldingQuestionAnswers",
[
{
answeredQuestions: [],
glassLocation: "Windshield",
glassName: "Single",
isSuppressedPart: undefined,
partNum: "FW04848",
},
],
false
);
wrapper.unmount();
});
test("Should trigger navigateForward", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
wrapper.unmount();
});
});
});
function setupMocks({
mountOptionsMockData = {
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
},
store: {
getters: store.getters,
commit: store.commit,
},
route: {
query: {
fmgPage: "molding-questions",
},
},
},
}) {
const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, vehicleQuestionsMixin],
});
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(moldingQuestions, mountOptions);
return { wrapper };
}

View file

@ -6,17 +6,17 @@
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader" :alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy" :alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="questions - required" validationRules="questions-required"
v-model="selectedAnswers" v-model="selectedAnswers"
@forwardButtonAction="forwardButtonAction" @forwardButtonAction="forwardButtonAction"
@backButtonAction="backButtonAction" @back-click="navigateBack"
:index="currentGlassIndex" /> :index="currentGlassIndex" />
</Form> </Form>
</template> </template>
<script> <script>
// Components // Components
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/common-components/layouts/questions-page-layout/questions-page-layout";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -67,37 +67,12 @@ export default {
AlertFewMoreQuestionsCopy() { AlertFewMoreQuestionsCopy() {
return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText"); return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText");
}, },
pageData() { partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS); return this.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS).partsOrQuestions;
}, },
}, },
mounted() { mounted() {
// are there alreadyAnsweredQuestions? this.getInitialQuestionData();
const alreadyAnsweredQuestions = store.getters.damage.moldingQuestionAnswers;
this.questionsData = this.pageData.partsOrQuestions
.filter((x) => x.parts[0].childPartQuestions.length)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.parts[0].childPartQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
@ -105,9 +80,46 @@ export default {
fmgPageValues.MOLDING_QUESTIONS fmgPageValues.MOLDING_QUESTIONS
); );
return ( return (
moldingQuestionsFromPageData && Object.keys(moldingQuestionsFromPageData).length > 0 // has childPartQuestions array and has glassName not null
moldingQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.glassName) &&
moldingQuestionsFromPageData.partsOrQuestions.some((glass) =>
glass.parts?.some((part) => part?.childPartQuestions?.length > 0)
)
); );
}, },
getInitialQuestionData() {
// get any questions that were already answered
const alreadyAnsweredQuestions = store.getters.damage.moldingQuestionAnswers;
this.questionsData = this.partsOrQuestionsData
.filter((x) => x.parts[0].childPartQuestions.length)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.parts[0].childPartQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(
glass,
index,
alreadyAnsweredQuestions
);
// Set up watch for each set of glass questions
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
},
async forwardButtonAction() { async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => { const questionAnswersArray = this.questionsData.map((glass) => {
return { return {
@ -132,7 +144,7 @@ export default {
); );
// get parts from the questionAnswers // get parts from the questionAnswers
let partsOrQuestions = this.pageData.partsOrQuestions; let partsOrQuestions = this.partsOrQuestionsData;
for (let answer of questionAnswersArray) { for (let answer of questionAnswersArray) {
partsOrQuestions.find((partOrQuestion) => { partsOrQuestions.find((partOrQuestion) => {
return ( return (

View file

@ -0,0 +1,427 @@
// Components
import partQuestions from "@/layouts/part-questions/part-questions";
// Supporting Files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { storeMutations } from "@/constants/store-mutations";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import baseMixin from "../../mixins/base-mixin";
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
import { nextTick } from "vue";
// 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(),
}));
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
jest.mock("@/helpers/heritage-integration/navigation-helper", () => ({
navigateToHeritageFunnel: jest.fn(),
}));
const baseStoreGettersPageData = () => {
return {
partsOrQuestions: [
{
parts: null,
partQuestions: [
{
questionSequence: 1,
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
answers: [
{
answerResult: "",
answerText: "Yes",
nextQuestionSequence: 2,
},
{
answerResult: "",
answerText: "No",
nextQuestionSequence: 3,
},
],
},
],
glassLocation: "Windshield",
glassName: "Single",
answerKey: "Windshield-Single",
answerData: null,
},
],
};
};
const baseStoreGettersDamage = () => {
return {
partsQuestionAnswers: [
{
glassLocation: "Windshield",
glassName: "Single",
result: "FW04848",
answeredQuestions: [
{
questionText:
"Is your vehicle equipped with the Panoramic Sunroof which can be identified by having a glass panel over the rear seats?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText:
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
},
],
};
};
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
store.commit = jest.fn();
describe("partQuestions.vue...", () => {
describe("method arePagePrerequisitesValid...", () => {
test("Should return true for valid page requisites if pageData exists", () => {
// Arrange
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBe(true);
wrapper.unmount();
});
test("Should return false for valid page requisites if partsOrQuestions in pageData is missing", () => {
// Arrange
const { wrapper } = setupMocks({});
store.getters.pageData = jest.fn(() => {
return undefined;
});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
test("Should be at least one item in partsOrQuestions", () => {
// Arrange
store.getters = {
pageData: jest.fn(() => {
return {
partsOrQuestions: [],
};
}),
damage: baseStoreGettersDamage,
};
const { wrapper } = setupMocks({});
// Act
const result = wrapper.vm.arePagePrerequisitesValid();
//Assert
expect(result).toBeFalsy();
wrapper.unmount();
});
});
describe("watch on selectedAnswers should be set up...", () => {
test("Should trigger handleAnswerUpdates if watched data changes", async () => {
// Arrange
store.getters = {
pageData: baseStoreGettersPageData,
damage: baseStoreGettersDamage,
};
const { wrapper } = setupMocks({});
const spy = jest.spyOn(wrapper.vm, "handleAnswerUpdates");
// Act
wrapper.setData({
selectedAnswers: {
"Windshield-Single": {
answerResult: "FW04848",
answeredQuestions: [
{
questionText: "One?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText: "Two?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
index: 0,
},
},
});
await nextTick();
wrapper.setData({
selectedAnswers: {
"Windshield-Single": {
answerResult: "NEW-ANSWER",
answeredQuestions: [
{
questionText: "One?",
selectedAnswerText: "Yes",
questionNum: 1,
},
{
questionText: "Two?",
selectedAnswerText: "Yes",
questionNum: 2,
},
],
index: 0,
},
},
});
//Assert
expect(spy).toHaveBeenCalled();
wrapper.unmount();
});
});
describe("forwardButtonAction", () => {
test("Should clear out answerData", async () => {
// Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
questionsData: [
{
glassLocation: "fbfWindshield",
glassName: "fbfSingle",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
],
});
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
wrapper.unmount();
});
test("Should save to Vuex store", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(spy).toHaveBeenNthCalledWith(
1,
"savePartQuestionAnswers",
[
{
answeredQuestions: [],
glassLocation: "Windshield",
glassName: "Single",
isSuppressedPart: undefined,
result: "FW04848",
},
],
false
);
wrapper.unmount();
});
test("Should call GET_PARTS API", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
// Act
wrapper.vm.forwardButtonAction();
await nextTick();
//Assert
expect(spy).toHaveBeenNthCalledWith(2, "getParts");
wrapper.unmount();
});
test("Should trigger navigateForward", async () => {
// Arrange
const { wrapper } = setupMocks({});
wrapper.vm.questionsData = [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
];
wrapper.vm.dispatchStoreAction = jest.fn(() => {
return {
data: {
glassPieceParts: [],
},
};
});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
//Assert
expect(wrapper.vm.navigateForward).toHaveBeenCalled();
wrapper.unmount();
});
});
});
function setupMocks({
mountOptionsMockData = {
router: {
navigate: jest.fn(),
navigateWithSaving: jest.fn(),
navigateWithoutSaving: jest.fn(),
},
actionList: [
{
actionName: storeActions.SAVE_PART_QUESTION_ANSWERS,
data: {},
},
{
actionName: storeActions.GET_PARTS,
data: {},
},
],
store: {
getters: store.getters,
commit: store.commit,
},
route: {
query: {
fmgPage: "part-questions",
},
},
data() {
return {
computedSwitcher: [
{
glassLocation: "Windshield",
glassName: "Single",
answerData: {
answerResult: "FW04848",
answeredQuestions: [],
},
},
],
};
},
questionsData: {
get() {
return this.computedSwitcher;
},
set(val) {
this.computedSwitcher = val;
},
},
},
}) {
const mountOptions = getMountOptions({
...mountOptionsMockData,
mixins: [baseMixin, vehicleQuestionsMixin],
});
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(partQuestions, mountOptions);
return { wrapper };
}

View file

@ -6,17 +6,17 @@
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader" :alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy" :alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
:questionsData="questionsData" :questionsData="questionsData"
:validationRules="questions - required" validationRules="questions-required"
v-model="selectedAnswers" v-model="selectedAnswers"
@forwardButtonAction="forwardButtonAction" @forwardButtonAction="forwardButtonAction"
@backButtonAction="backButtonAction" @back-click="navigateBack"
:index="currentGlassIndex" /> :index="currentGlassIndex" />
</Form> </Form>
</template> </template>
<script> <script>
// Components // Components
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout"; import questionsPageLayout from "@/common-components/layouts/questions-page-layout/questions-page-layout";
// Supporting Files // Supporting Files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -67,61 +67,77 @@ export default {
AlertFewMoreQuestionsCopy() { AlertFewMoreQuestionsCopy() {
return this.getCmsContent("AlertPartsQuestions", "BodyText"); return this.getCmsContent("AlertPartsQuestions", "BodyText");
}, },
pageData() { partsOrQuestionsData() {
return this.$store.getters.pageData(fmgPageValues.PART_QUESTIONS); return this.$store.getters.pageData(fmgPageValues.PART_QUESTIONS).partsOrQuestions;
}, },
}, },
mounted() { mounted() {
// are there alreadyAnsweredQuestions? this.getInitialQuestionData();
const alreadyAnsweredQuestions = store.getters.damage.partQuestionAnswers;
this.questionsData = this.pageData.partsOrQuestions
.filter((x) => x.partQuestions)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.partQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(glass, index, alreadyAnsweredQuestions);
// Set up watch for each set of glass questions
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
}, },
methods: { methods: {
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
const partQuestionsFromPageData = store.getters.pageData(fmgPageValues.PART_QUESTIONS); const partQuestionsFromPageData = store.getters.pageData(fmgPageValues.PART_QUESTIONS);
return partQuestionsFromPageData && Object.keys(partQuestionsFromPageData).length > 0; return (
// has .partQuestions array and has glassName not null
partQuestionsFromPageData?.partsOrQuestions?.some((part) => part?.glassName) &&
partQuestionsFromPageData?.partsOrQuestions?.some(
(part) => part?.partQuestions?.length > 0
)
);
},
getInitialQuestionData() {
// get any questions that were already answered
const alreadyAnsweredQuestions = store.getters.damage.partQuestionAnswers;
this.questionsData = this.partsOrQuestionsData
.filter((x) => x.partQuestions)
.map((glass, index) => {
// NOTE: questions for property "questions" can differ between layouts
glass.questions = glass.partQuestions;
glass.answerKey = glass.glassLocation + "-" + glass.glassName;
// reset selectedAnswers for this glass
this.selectedAnswers[glass.answerKey] = [];
const updatedGlass = this.setupInitialData(
glass,
index,
alreadyAnsweredQuestions
);
// Set up watch for each set of glass questions
this.$watch(
"selectedAnswers." + glass.answerKey,
(newValue) => {
if (newValue && Object.keys(newValue).length > 0) {
this.handleAnswerUpdates(newValue, glass.answerKey);
}
},
{ deep: true }
);
return updatedGlass;
});
}, },
async forwardButtonAction() { async forwardButtonAction() {
const questionAnswersArray = this.questionsData.map((glass) => { const questionAnswersArray = this.questionsData.map((glass) => {
return { return {
glassLocation: glass.glassLocation, glassLocation: glass.glassLocation,
glassName: glass.glassName, glassName: glass.glassName,
result: glass.answerData.answerResult, result: (glass.answerData && glass.answerData.answerResult) || "",
answeredQuestions: glass.answerData.answeredQuestions, answeredQuestions: glass.answerData?.answeredQuestions,
isSuppressedPart: glass.isSuppressedPart, isSuppressedPart: glass.isSuppressedPart,
}; };
}); });
// clear out answerData for future page loads; must occur prior to store save // clear out answerData for future page loads; must occur prior to store save
this.questionsData.forEach((glass) => { this.questionsData.forEach((glass) => {
glass.answerData = {}; if (glass.answerData) {
glass.answerData = {};
}
}); });
// save to vuex store as order.damage.partQuestionAnswers (array) // save to vuex store as order.damage.partQuestionAnswers (array)
// used in GET_PARTS call following this one
await this.dispatchStoreAction( await this.dispatchStoreAction(
this.storeActions.SAVE_PART_QUESTION_ANSWERS, this.storeActions.SAVE_PART_QUESTION_ANSWERS,
questionAnswersArray, questionAnswersArray,

View file

@ -59,8 +59,9 @@ import modal from "@/common-components/modal/modal";
import loadingModal from "@/common-components/loading-modal/loading-modal.vue"; import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";
import { settleAllPromises } from "@/helpers/layout-helper";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import store from "@/store";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { Form } from "vee-validate"; import { Form } from "vee-validate";
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
@ -70,22 +71,48 @@ export default {
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
// Call APIs // Call APIs
const cmsContent = await fetchCmsContentForPage(to.query.fmgPage); const cmsContent = await fetchCmsContentForPage(to.query.fmgPage);
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_RAIN_DEFENSE
);
const supportingItemsPromise = baseMixin.methods.dispatchStoreAction(
storeActions.GET_SUPPORTING_ITEMS
);
// TODOS - modify as needed, just a rough sketch to place initializations const promiseResultMap = [
{
resultKey: "wipers",
promise: wipersPromise,
},
{
resultKey: "rainDefense",
promise: rainDefensePromise,
},
{
resultKey: "supportingItems",
promise: supportingItemsPromise,
},
];
// get supporting availableLineItems const resultMap = await settleAllPromises(promiseResultMap);
const glassParts = store.getters.order.lineItems.glassParts ?? [];
const availableLineItems = [
resultMap.rainDefense,
...resultMap.supportingItems,
...resultMap.wipers,
...glassParts,
];
// get wiper and rain defense availableLineItems const pricingResults = await baseMixin.methods.dispatchStoreAction(
storeActions.PRICE_ORDER_ITEMS,
// Settle promises and get results availableLineItems,
false
// get price of supporting, rain defense & wipers, and parts );
// Settle pricing promise and get results
// Call the "next" function to complete the transition to this page. // Call the "next" function to complete the transition to this page.
next((vm) => { next((vm) => {
vm.setCmsContent(cmsContent); vm.setCmsContent(cmsContent);
vm.availableLineItems = [ vm.availableLineItems = [
{ {
partNumber: "001", partNumber: "001",
@ -135,15 +162,20 @@ export default {
}, },
]; ];
vm.supportingItems = resultMap.supportingItems;
vm.availableLineItems = pricingResults;
vm.isInsuranceSelected = vm.getDefaultIsInsuranceSelectedValue();
vm.dispatchStoreAction(storeActions.SAVE_IS_INSURANCE, vm.isInsuranceSelected); vm.dispatchStoreAction(storeActions.SAVE_IS_INSURANCE, vm.isInsuranceSelected);
}); });
}, },
data() { data() {
return { return {
pricingRequestResult: null,
isInsuranceSelected: null, isInsuranceSelected: null,
selectedPackage: null, selectedPackage: null,
availableLineItems: null, availableLineItems: null,
supportingItems: null,
}; };
}, },
methods: { methods: {
@ -162,14 +194,18 @@ export default {
} }
}, },
backButtonAction() { backButtonAction() {
vehicleQuestionsMixin.methods.backButtonAction(this); vehicleQuestionsMixin.methods.navigateBack(this);
}, },
forwardButtonAction() { async forwardButtonAction() {
// TODO KO if VAPS package is not selected, return await this.dispatchStoreAction(
this.storeActions.SAVE_QUOTE_PAGE_SELECTIONS,
// TODO KO is VAPS package is selected, add the items as needed {
isInsuranceSelected: this.isInsuranceSelected,
this.dispatchStoreAction(storeActions.SAVE_IS_INSURANCE, this.isInsuranceSelected); vapsItemsToAdd: this.selectedPackage,
supportingItemsToAdd: this.supportingItems,
},
false
);
navigateToHeritageFunnel({loadingModal: this.$refs.loadingModal}); navigateToHeritageFunnel({loadingModal: this.$refs.loadingModal});
}, },

View file

@ -0,0 +1,580 @@
export const mockProcessedCmsContent = {
"05_01_CSR_Quote_Standard_Repair": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_Recal": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_RearGlass": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_RearGlass+NonWindshield": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_RearGlass+Windshield": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_RearGlassNoFrontFit": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_Windshield+SideGlass": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_SideGlass": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement glass</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
"05_01_CSR_Quote_NoWiperFit": {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
badCustomValue: {
EconomyServicePackage: {
HeaderText: "Economy service",
SubheaderText: "",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li></ul>",
Image: "",
FooterText:
"{if:custom:frontWipersApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{else}{if:custom:rearWiperApplicableForTierTwo}New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.{end}{end}",
},
StandardServicePackage: {
HeaderText: "Standard service",
SubheaderText: "MOST POPULAR",
BodyText:
"<ul><li>New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierTwo}{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierTwo}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li></ul>",
Image: "",
FooterText: "",
},
PremiumServicePackage: {
HeaderText: "Premium service",
SubheaderText: "",
BodyText:
"<ul><li>{if:custom:badCustomValue}DO NOT SHOW{end}New replacement windshield</li><li>Expert installation{if:custom:isRecalibrationOnOrder} and {textLink:EstimateRecalModal,recalibration}{end}</li><li>Nationwide lifetime warranty</li><li>{if:custom:frontWipersApplicableForTierThree}{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateFrontWiperModal,front wiper blades}{else}New {textLink:EstimateFrontWiperModal,wiper blades}{end}{end}</li><li>{if:custom:rearWiperApplicableForTierThree}New {textLink:EstimateRearWiperModal,rear wiper blade}{end}</li><li>{if:custom:rainDefenseApplicableForTierThree}{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment{end}</li></ul>",
Image: "",
FooterText: "",
},
},
};
export const inputQuestionWidgetAnswers = {
CashServicePackageQuestionWidget: {
Answers: [
{
Name: "TierOne",
SubWidgetName: "EconomyServicePackage",
},
{
Name: "TierTwo",
SubWidgetName: "StandardServicePackage",
},
{
Name: "TierThree",
SubWidgetName: "PremiumServicePackage",
},
],
},
InsuranceServicePackageQuestionWidget: {
Answers: [
{
Name: "TierOne",
SubWidgetName: "EconomyServicePackage",
},
{
Name: "TierTwo",
SubWidgetName: "StandardServicePackage",
},
{
Name: "TierThree",
SubWidgetName: "PremiumServicePackage",
},
],
},
};
export const expectedModifiedAnswers = {
"05_01_CSR_Quote_Standard_Repair": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li></ul>",
buttonAuxillaryCopy: "$500.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li></ul>",
buttonAuxillaryCopy: "$585.90",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>Expert windshield repair</li><li>Exclusive resin sealant</li><li>Nationwide lifetime guarantee</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$621.40",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_Recal": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation and {textLink:EstimateRecalModal,recalibration}</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$500.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation and {textLink:EstimateRecalModal,recalibration}</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li></ul>",
buttonAuxillaryCopy: "$585.90",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation and {textLink:EstimateRecalModal,recalibration}</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$621.40",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_RearGlass": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li></ul>",
buttonAuxillaryCopy: "$374.70",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,front wiper blades}</li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li><li></li></ul>",
buttonAuxillaryCopy: "$460.38",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_RearGlass+NonWindshield": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li></ul>",
buttonAuxillaryCopy: "$374.70",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,front wiper blades}</li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li><li></li></ul>",
buttonAuxillaryCopy: "$460.38",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_RearGlass+Windshield": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,front wiper blades}</li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li></ul>",
buttonAuxillaryCopy: "$460.38",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,front wiper blades}</li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$495.88",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_RearGlassNoFrontFit": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li></ul>",
buttonAuxillaryCopy: "$374.70",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li>New {textLink:EstimateRearWiperModal,rear wiper blade}</li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$410.20",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_Windshield+SideGlass": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy:
"New wiper blades are not included with Economy service. Because chips can disrupt the glass surface, we recommend new wipers after a repair.",
},
tierTwo: {
value: "TierTwo",
buttonLabel: "Standard service",
buttonLabelSubCopy: "MOST POPULAR",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li></ul>",
buttonAuxillaryCopy: "$435.90",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$471.40",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_SideGlass": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement glass</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li>New {textLink:EstimateFrontWiperModal,wiper blades}</li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$471.40",
buttonFooterCopy: "",
},
},
"05_01_CSR_Quote_NoWiperFit": {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$385.72",
buttonFooterCopy: "",
},
},
badCustomValue: {
tierOne: {
value: "TierOne",
buttonLabel: "Economy service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation</li><li>Nationwide lifetime warranty</li></ul>",
buttonAuxillaryCopy: "$350.22",
buttonFooterCopy: "",
},
tierThree: {
value: "TierThree",
buttonLabel: "Premium service",
buttonLabelSubCopy: "",
buttonBodyCopy:
"<ul><li>New replacement windshield</li><li>Expert installation</li><li>Nationwide lifetime warranty</li><li></li><li></li><li>{textLink:EstimateRainDefenseModal,Rain Defense}&trade; treatment</li></ul>",
buttonAuxillaryCopy: "$385.72",
buttonFooterCopy: "",
},
},
};

View file

@ -0,0 +1,602 @@
import { shallowMount, mount } from "@vue/test-utils";
import servicePackageQuestion from "./service-package-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import {
expectedModifiedAnswers,
mockProcessedCmsContent,
inputQuestionWidgetAnswers,
} from "./service-package-question-test-helper";
jest.mock("@/store", () => ({
commit: jest.fn(),
dispatch: jest.fn(),
}));
describe("service-package-question.vue", () => {
beforeEach(async () => {
processedCmsContent = inputQuestionWidgetAnswers;
mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return processedCmsContent[widgetName][cmsFieldName];
}),
},
};
mockProps = {
modelValue: "initialValue",
isInsuranceSelected: false,
insuranceCmsWidgetName: "InsuranceServicePackageQuestionWidget",
cashCmsWidgetName: "CashServicePackageQuestionWidget",
availableLineItems: [
{
partNumber: "001",
Description: "Windshield with Recal",
partType: "Windshield",
Quantity: "1",
BasePartNumber: "001",
Color: "Green",
CanSafeliteRecalibrate: true,
price: 350.22,
},
{
partNumber: "RAIN DEFENSE",
description: null,
partType: "RAIN DEFENSE",
price: 35.5,
},
],
};
const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
});
it("should emit relevant VAPS items in an array", () => {
// Arrange
const wrapper = setupMocks({});
// Act
wrapper.componentVM.selectedValues = "TierThree";
// Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([
{
description: null,
partNumber: "RAIN DEFENSE",
partType: "RAIN DEFENSE",
price: 35.5,
},
]);
});
it("should have the correct insurance pricing text when insurance is selected", () => {
// Arrange
mockProps.isInsuranceSelected = true;
const wrapper = setupMocks({});
// Act
// Assert
expect(
wrapper.vm.servicePackageAnswers[0].buttonAuxillaryCopy.includes("As little as")
).toBe(true);
});
it("should return [] from nullSafeAvailableLineItems and not error out if availableLineItems is null", () => {
// Arrange
mockProps.availableLineItems = null;
const wrapper = setupMocks({});
// Act
// Assert
expect(wrapper.vm.nullSafeAvailableLineItems).toEqual([]);
});
it("should return a null servicePackageAnswers if the cmsContent is falsy", () => {
// Arrange
mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return null;
}),
},
};
const wrapper = setupMocks({});
// Assert
expect(wrapper.vm.servicePackageAnswers).toBe(null);
});
it("should treat an undefined 'getCustomValueFromString' as a false value and not error out", () => {
// Arrange
const packageNameKey = "badCustomValue";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Windshield" }],
},
},
hasAnyNonWindshieldGlassParts: false,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer should not be created
expect(wrapper.vm.servicePackageAnswers.length).toBe(2);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
});
describe("service-package-question.vue, matching business rules for package display", () => {
// mock scenarios in figma:
// https://www.figma.com/file/Spt9hBtGj8r8PFFGNIN3G5/New-Funnel?node-id=100%3A11383
beforeEach(async () => {
processedCmsContent = inputQuestionWidgetAnswers;
mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return processedCmsContent[widgetName][cmsFieldName];
}),
},
};
mockProps = {
modelValue: "initialValue",
isInsuranceSelected: false,
insuranceCmsWidgetName: "InsuranceServicePackageQuestionWidget",
cashCmsWidgetName: "CashServicePackageQuestionWidget",
availableLineItems: [
{
partNumber: "001",
Description: "Windshield with Recal",
partType: "Windshield",
Quantity: "1",
BasePartNumber: "001",
Color: "Green",
CanSafeliteRecalibrate: true,
price: 350.22,
},
{
partNumber: "RAIN DEFENSE",
description: null,
partType: "RAIN DEFENSE",
price: 35.5,
},
],
};
});
it("should match the 05_01_CSR_Quote_Standard_Repair mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_Standard_Repair";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: true,
glassToReplace: [],
},
},
hasAnyNonWindshieldGlassParts: false,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_Recal mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_Recal";
mockProps.availableLineItems.push(recalLineItem);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Windshield" }],
},
},
hasAnyNonWindshieldGlassParts: false,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_RearGlass mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_RearGlass";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Rear" }],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_RearGlass+NonWindshield mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_RearGlass+NonWindshield";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [
{ glassLocation: "Rear" },
{ glassLocation: "Driver" },
],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
//console.log(wrapper.vm.servicePackageAnswers);
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_RearGlass+Windshield mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_RearGlass+Windshield";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [
{ glassLocation: "Rear" },
{ glassLocation: "Windshield" },
],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_RearGlassNoFrontFit mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_RearGlassNoFrontFit";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Rear" }],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_Windshield+SideGlass mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_Windshield+SideGlass";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [
{ glassLocation: "Driver" },
{ glassLocation: "Windshield" },
],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierTwo
);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[2],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_SideGlass mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_SideGlass";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
mockProps.availableLineItems.push(driverFrontWiperLineItem);
mockProps.availableLineItems.push(passengerFrontWiperLineItem);
mockProps.availableLineItems.push(rearWiperLineItem);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Driver" }],
},
},
hasAnyNonWindshieldGlassParts: true,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer should not be created
expect(wrapper.vm.servicePackageAnswers.length).toBe(2);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
it("should match the 05_01_CSR_Quote_NoWiperFit mock", () => {
// Arrange
const packageNameKey = "05_01_CSR_Quote_NoWiperFit";
Object.assign(processedCmsContent, mockProcessedCmsContent[packageNameKey]);
const wrapper = setupMocks({
mountOptionsMockData: {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Windshield" }],
},
},
hasAnyNonWindshieldGlassParts: false,
},
},
},
});
// Assert
// economy answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[0],
expectedModifiedAnswers[packageNameKey].tierOne
);
// standard answer should not be created
expect(wrapper.vm.servicePackageAnswers.length).toBe(2);
// premium answer
runPackageAnswerExpectStatements(
wrapper.vm.servicePackageAnswers[1],
expectedModifiedAnswers[packageNameKey].tierThree
);
});
});
function runPackageAnswerExpectStatements(servicePackageAnswer, expectedOutput) {
expect(servicePackageAnswer.buttonLabel).toBe(expectedOutput.buttonLabel);
expect(servicePackageAnswer.buttonLabelSubCopy).toBe(expectedOutput.buttonLabelSubCopy);
expect(servicePackageAnswer.buttonBodyCopy).toBe(expectedOutput.buttonBodyCopy);
expect(servicePackageAnswer.buttonFooterCopy).toBe(expectedOutput.buttonFooterCopy);
}
function setupMocks({ mountOptionsMockData, props = mockProps }) {
var mountOptionsMockDataDefault = {
store: {
getters: {
order: {
damage: {
isRepair: false,
glassToReplace: [{ glassLocation: "Windshield" }],
},
},
hasAnyNonWindshieldGlassParts: false,
},
},
};
mountOptionsMockData = Object.assign(mountOptionsMockDataDefault, mountOptionsMockData);
const mountOptions = getMountOptions(mountOptionsMockData);
// Modify/augment default mount options
mountOptions.global.mixins = [mockMixin];
mountOptions.propsData = props;
const wrapper = shallowMount(servicePackageQuestion, mountOptions);
return wrapper;
}
///////////////
// Constants //
///////////////
let processedCmsContent;
let mockMixin;
let mockProps;
let recalLineItem = {
partNumber: "RECAL STATIC",
Description: "Recalibration",
partType: "recalibration",
Quantity: "1",
price: 150.0,
};
let driverFrontWiperLineItem = {
partNumber: "SBB16",
description: "SAFELITE BEAM BLADE 16",
partType: "FRONT WIPER",
price: 32.64,
};
let passengerFrontWiperLineItem = {
partNumber: "SBB26",
description: "SAFELITE BEAM BLADE 26",
partType: "FRONT WIPER",
price: 53.04,
};
let rearWiperLineItem = {
partNumber: "SBBR12A",
description: "SAFELITE REAR BLADE 12A",
partType: "REAR WIPER",
price: 24.48,
};

View file

@ -43,7 +43,9 @@ export default {
return this.modelValue; return this.modelValue;
}, },
set: function (newValue) { set: function (newValue) {
this.$emit("update:modelValue", newValue); const VapsProductsInSelectedPackage =
this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit("update:modelValue", VapsProductsInSelectedPackage);
}, },
}, },
nullSafeAvailableLineItems() { nullSafeAvailableLineItems() {
@ -206,6 +208,38 @@ export default {
}); });
return vapsPrice; return vapsPrice;
}, },
getVapsLineItemsForSelectedPackage(packageName) {
const vapsLineItemsForSelectedPackage = [];
if (packageName === packageNames.TIER_TWO) {
if (this.frontWipersApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
}
if (this.rearWiperApplicableForTierTwo) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
}
} else if (packageName === packageNames.TIER_THREE) {
if (this.frontWipersApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.FRONT_WIPER)
);
}
if (this.rearWiperApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.REAR_WIPER)
);
}
if (this.rainDefenseApplicableForTierThree) {
vapsLineItemsForSelectedPackage.push(
...this.getLineItemsContainingPartType(partTypeStrings.RAIN_DEFENSE)
);
}
}
return vapsLineItemsForSelectedPackage;
},
getCustomValueFromString(str) { getCustomValueFromString(str) {
switch (str) { switch (str) {
case "isRecalibrationOnOrder": case "isRecalibrationOnOrder":
@ -224,10 +258,14 @@ export default {
return null; return null;
} }
}, },
lineItemsContainsPartType(partType) { getLineItemsContainingPartType(partType) {
const partTypeMatches = this.nullSafeAvailableLineItems.filter( const partTypeMatches = this.nullSafeAvailableLineItems.filter(
(lineItem) => lineItem.partType.toUpperCase() === partType (lineItem) => lineItem.partType.toUpperCase() === partType
); );
return partTypeMatches;
},
lineItemsContainsPartType(partType) {
const partTypeMatches = this.getLineItemsContainingPartType(partType);
return !!partTypeMatches.length; return !!partTypeMatches.length;
}, },
glassToReplaceContainsGlassLocation(glassLocation) { glassToReplaceContainsGlassLocation(glassLocation) {

View file

@ -0,0 +1,134 @@
import { mount } from "@vue/test-utils";
import servicePackageRadio from "./service-package-radio";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
describe("service-package-radio.vue", () => {
it("Should include buttonLabel in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabel"]));
});
it("Should include buttonLabelAuxillaryCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelAuxillaryCopy"]));
});
it("Should include buttonLabelSubCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonLabelSubCopy"]));
});
it("Should include buttonFooterCopy in html", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const outputHtml = wrapper.html();
// Assert
expect(outputHtml).toEqual(expect.stringContaining(mockProps["buttonFooterCopy"]));
});
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when provided with a <ul><li>...</li>(x5)</ul> buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
it("Should get strings from getArrayOfListItemsFromRawCmsCopy without <ul> or <li> tags when provided with a <ul><li>...</li></ul> buttonBodyCopy", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: mockProps,
},
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
const fileredResults = results.filter((result) => {
return (
result.includes("<ul>") ||
result.includes("</ul>") ||
result.includes("<li>") ||
result.includes("</li>")
);
});
expect(fileredResults.length).toBe(0);
});
it("Should get 5 strings from getArrayOfListItemsFromRawCmsCopy when buttonBodyCopy has 6 total <li>...</li>, but one is empty ", async () => {
// Arrange
const moddedProps = mockProps;
moddedProps["buttonBodyCopy"] =
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li><li></li></ul>";
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: moddedProps,
},
});
// Act
const results = wrapper.vm.getArrayOfListItemsFromRawCmsCopy(wrapper.vm.buttonBodyCopy);
// Assert
expect(results.length).toBe(5);
});
});
const mockProps = {
buttonLabel: "buttonLabel test copy",
buttonLabelAuxillaryCopy: "buttonLabelAuxillaryCopy test copy",
buttonLabelSubCopy: "buttonLabelSubCopy test copy",
buttonBodyCopy:
"<ul><li>buttonBodyCopy test copy</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>",
buttonFooterCopy: "buttonFooterCopy test copy",
};
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(servicePackageRadio, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -1,5 +1,5 @@
<template> <template>
<baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange"> <baseInputButton v-bind="$props" @buttonClicked="handleAnswerChange" v-model="selectedValue">
<div class="package-label" for="testradio"> <div class="package-label" for="testradio">
<div class="package-specs"> <div class="package-specs">
<p :class="[this.buttonLabelSubCopy ? 'mb-2' : 'm-0']"> <p :class="[this.buttonLabelSubCopy ? 'mb-2' : 'm-0']">
@ -35,7 +35,7 @@
</ul> </ul>
<!-- End of body text parsing --> <!-- End of body text parsing -->
<div <div
class="package-footer" class="package-footer fw-bold caption ms-n6"
v-if="this.buttonFooterCopy" v-if="this.buttonFooterCopy"
v-html="this.buttonFooterCopy"></div> v-html="this.buttonFooterCopy"></div>
</div> </div>
@ -179,10 +179,7 @@ export default {
} }
.package-footer { .package-footer {
font-size: 12px; color: $red;
font-weight: 500;
color: #d4281c;
margin-left: -32px;
} }
.package-specs { .package-specs {

View file

@ -196,7 +196,7 @@ describe("vehicle-parts.vue", () => {
}); });
}); });
test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => { test("User had part questions > navigateBack triggers a router.navigateWithoutSaving change with correct scenario", async () => {
//Arrange //Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mountOptionsMockData: { mountOptionsMockData: {
@ -248,7 +248,7 @@ describe("vehicle-parts.vue", () => {
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
//Assert //Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -257,7 +257,7 @@ describe("vehicle-parts.vue", () => {
); );
}); });
test("User did not have part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => { test("User did not have part questions > navigateBack triggers a router.navigateWithoutSaving change with correct scenario", async () => {
//Arrange //Arrange
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mountOptionsMockData: { mountOptionsMockData: {
@ -294,7 +294,7 @@ describe("vehicle-parts.vue", () => {
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
//Assert //Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -497,7 +497,7 @@ describe("vehicle-parts.vue", () => {
// TODO KO UNCOMMENT FOR QUOTE PAGES RELEASE // TODO KO UNCOMMENT FOR QUOTE PAGES RELEASE
// expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( // expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
// navigationScenarios.HAS_NO_MORE_QUESTIONS, // navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
// { query: { fmgPage: "vehicle-parts" } } // { query: { fmgPage: "vehicle-parts" } }
// ); // );
}); });

View file

@ -36,7 +36,7 @@
ref="funnelFooter" ref="funnelFooter"
:isForwardActionDisabled="isForwardActionDisabled" :isForwardActionDisabled="isForwardActionDisabled"
@tempButtonClicked="() => handleTempButtonClicked(this)" @tempButtonClicked="() => handleTempButtonClicked(this)"
@back-clicked="backButtonAction" @back-click="navigateBack"
@ForwardClicked="forwardButtonAction" /> @ForwardClicked="forwardButtonAction" />
</div> </div>
</div> </div>

View file

@ -1,9 +1,6 @@
<template> <template>
<div class="vin-information"> <div class="vin-information">
<div <div class="vin-toggle mt-2" :class="[isActive ? 'active' : '']" @click="toggleClass()">
class="vin-toggle d-inline-flex mt-2"
:class="[isActive ? 'active' : '']"
@click="toggleClass()">
<textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" /> <textLink linkType="text" href="#!" :text="WhereCanIFindMyVINHeadline" />
</div> </div>
<div class="vin-info"> <div class="vin-info">

View file

@ -168,7 +168,7 @@ describe("vin-lookup.vue", () => {
//Assert //Assert
expect(wrapper.vm.$router.navigateWithSaving).toBeCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toBeCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
wrapper.vm.$route, wrapper.vm.$route,
expect.anything(), expect.anything(),
expect.anything() expect.anything()

View file

@ -370,7 +370,7 @@ export default {
async navigateForward() { async navigateForward() {
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) { if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
this.$router.navigateWithSaving( this.$router.navigateWithSaving(
this.navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, this.navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
this.$route, this.$route,
{}, {},
{ [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true } { [routerParams.DISPLAY_VEHICLE_CHANGE_ALERT]: true }

View file

@ -42,6 +42,8 @@ export default {
partNumber: singlePart.partNumber, partNumber: singlePart.partNumber,
description: singlePart.description, description: singlePart.description,
color: singlePart.color, color: singlePart.color,
partType: singlePart.partType,
canSafeliteRecalibrate: singlePart.canSafeliteRecalibrate,
requiresRecalibration: singlePart.requiresRecalibration, requiresRecalibration: singlePart.requiresRecalibration,
requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions, requiresCapabilityQuestions: singlePart.requiresCapabilityQuestions,
recalibrationType: singlePart.recalibrationType, recalibrationType: singlePart.recalibrationType,
@ -378,7 +380,7 @@ export default {
this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS) this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS)
) { ) {
self.$router.navigateWithSaving( self.$router.navigateWithSaving(
self.navigationScenarios.HAS_PART_QUESTIONS, self.navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
self.$route, self.$route,
{}, {},
{}, {},
@ -391,7 +393,7 @@ export default {
// if multiple parts on any glass // if multiple parts on any glass
// go to vehicle-parts page and pass the partsData // go to vehicle-parts page and pass the partsData
self.$router.navigateWithSaving( self.$router.navigateWithSaving(
self.navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, self.navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
self.$route, self.$route,
{}, {},
{}, {},
@ -404,7 +406,7 @@ export default {
// if any childpart questions // if any childpart questions
// go to molding-questions page and pass the partsData // go to molding-questions page and pass the partsData
self.$router.navigateWithSaving( self.$router.navigateWithSaving(
self.navigationScenarios.HAS_MOLDING_QUESTIONS, self.navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
self.$route, self.$route,
{}, {},
{}, {},
@ -444,7 +446,7 @@ export default {
} }
self.$router.navigateWithSaving( self.$router.navigateWithSaving(
self.navigationScenarios.HAS_CAPABILITY_QUESTIONS, self.navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
self.$route, self.$route,
{}, {},
{}, {},
@ -464,19 +466,19 @@ export default {
shouldGoToHeritageQuote || (payment.isInsurance && payment.insuranceCoverage.isVerified) shouldGoToHeritageQuote || (payment.isInsurance && payment.insuranceCoverage.isVerified)
? navigateToHeritageFunnel({loadingModal: self.$refs.loadingModal}) ? navigateToHeritageFunnel({loadingModal: self.$refs.loadingModal})
: self.$router.navigateWithSaving( : self.$router.navigateWithSaving(
self.navigationScenarios.HAS_NO_MORE_QUESTIONS, self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
self.$route self.$route
); );
} }
}, },
// Can't use `this` because navigateForward is also called from quote // Can't use `this` because navigateForward is also called from quote
backButtonAction(vm) { navigateBack(vm) {
const self = vm ?? this; const self = vm ?? this;
const partsOrQuestions = ( const partsOrQuestions = (
self.$store.getters.pageData(fmgPageValues.PART_QUESTIONS) ?? self.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS) ??
self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS) ??
self.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS) ?? self.$store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS) ??
self.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS) self.$store.getters.pageData(fmgPageValues.VEHICLE_PARTS) ??
self.$store.getters.pageData(fmgPageValues.PART_QUESTIONS)
)?.partsOrQuestions; )?.partsOrQuestions;
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions); const hasPartQuestions = this.hasPartQuestions(partsOrQuestions);
const hasGlassLocationWithMultipleParts = const hasGlassLocationWithMultipleParts =

View file

@ -1261,7 +1261,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_PART_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1376,7 +1376,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_PART_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1483,7 +1483,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_PART_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1638,7 +1638,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_PART_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1686,7 +1686,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1798,7 +1798,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -1976,7 +1976,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -2035,7 +2035,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_MOLDING_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -2077,7 +2077,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_CAPABILITY_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
wrapper.vm.$route, wrapper.vm.$route,
{}, {},
{}, {},
@ -2124,7 +2124,7 @@ describe("vehicle-questions-mixin", () => {
// Assert // Assert
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_NO_MORE_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
{ query: { fmgPage: "vin-lookup" } } { query: { fmgPage: "vin-lookup" } }
); );
expect(wrapper.vm.$router.navigateWithoutSaving).not.toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithoutSaving).not.toHaveBeenCalled();
@ -2235,7 +2235,7 @@ describe("vehicle-questions-mixin", () => {
); );
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
navigationScenarios.HAS_NO_MORE_QUESTIONS, navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
{ query: { fmgPage: "vin-lookup" } } { query: { fmgPage: "vin-lookup" } }
); );
expect(wrapper.vm.$router.navigateWithoutSaving).not.toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithoutSaving).not.toHaveBeenCalled();
@ -2243,7 +2243,7 @@ describe("vehicle-questions-mixin", () => {
}); });
}); });
describe("backButtonAction", () => { describe("navigateBack", () => {
// TODO KO // TODO KO
test.todo( test.todo(
"current page is quote, there are no questions, and we don't have their vin => go to estimate" "current page is quote, there are no questions, and we don't have their vin => go to estimate"
@ -2254,7 +2254,7 @@ describe("vehicle-questions-mixin", () => {
const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE, hasVin: true }); const { wrapper } = setupMocks({ fmgPage: fmgPageValues.QUOTE, hasVin: true });
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2269,7 +2269,7 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2285,7 +2285,7 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasChildPartQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2303,7 +2303,7 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
@ -2321,7 +2321,7 @@ describe("vehicle-questions-mixin", () => {
wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true); wrapper.vm.hasCapabilityQuestions = jest.fn().mockReturnValue(true);
// Act // Act
wrapper.vm.backButtonAction(); wrapper.vm.navigateBack();
// Assert // Assert
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalledWith(

View file

@ -1,3 +1,11 @@
/**
* We want to name these to describe the scenario rather than what page(s) they're used on.
*
* Naming convention: [PAST_TENSE_VERB]_[DESCRIPTOR_OF_STATE]
* ex: Rather than "HAS_PART_QUESTIONS", we might name the
* scenario "CLICKED_FORWARD_WITH_PART_QUESTIONS"
*/
const navigationScenarios = { const navigationScenarios = {
// General // General
CLICKED_BACK: "CLICKED_BACK", CLICKED_BACK: "CLICKED_BACK",
@ -9,31 +17,29 @@ const navigationScenarios = {
SELECTED_MAKE: "SELECTED_MAKE", SELECTED_MAKE: "SELECTED_MAKE",
SELECTED_STYLE: "SELECTED_STYLE", SELECTED_STYLE: "SELECTED_STYLE",
// Vin pages // Vin selection
CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN", CLICKED_BACK_WITH_VIN: "CLICKED_BACK_WITH_VIN",
CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN", CLICKED_FORWARD_WITH_VIN: "CLICKED_FORWARD_WITH_VIN",
CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE: "CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE", CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE: "CLICKED_FORWARD_WITH_REPAIR_AND_VERIFIED_INSURANCE",
CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE: "CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE", CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE: "CLICKED_FORWARD_WITH_REPLACE_AND_VERIFIED_INSURANCE",
CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN", CLICKED_FORWARD_WITHOUT_VIN: "CLICKED_FORWARD_WITHOUT_VIN",
CONTINUING_WITH_MULTIPLE_VEHICLES: "CONTINUING_WITH_MULTIPLE_VEHICLES", CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES: "CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES",
SELECTED_VIN_HAS_MISMATCHED_GLASS: "SELECTED_VIN_HAS_MISMATCHED_GLASS", SELECTED_VIN_WITH_MISMATCHED_GLASS: "SELECTED_VIN_WITH_MISMATCHED_GLASS",
SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN", SELECTED_MANUAL_VIN: "SELECTED_MANUAL_VIN",
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE", SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",
SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS", SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS",
HAS_NO_QUESTIONS: "HAS_NO_QUESTIONS", CLICKED_FORWARD_WITH_NO_QUESTIONS: "CLICKED_FORWARD_WITH_NO_QUESTIONS",
// Question pages // Part selection
HAS_PART_QUESTIONS: "HAS_PART_QUESTIONS", CLICKED_FORWARD_WITH_PART_QUESTIONS: "CLICKED_FORWARD_WITH_PART_QUESTIONS",
HAS_MULTIPLE_PARTS_TO_CHOOSE: "HAS_MULTIPLE_PARTS_TO_CHOOSE", CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE: "CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE",
HAS_MOLDING_QUESTIONS: "HAS_MOLDING_QUESTIONS", CLICKED_FORWARD_WITH_MOLDING_QUESTIONS: "CLICKED_FORWARD_WITH_MOLDING_QUESTIONS",
HAS_CAPABILITY_QUESTIONS: "HAS_CAPABILITY_QUESTIONS", CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS: "CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS",
HAS_NO_MORE_QUESTIONS: "HAS_NO_MORE_QUESTIONS", CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS: "CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS",
CLICKED_BACK_WITH_PART_QUESTIONS: "CLICKED_BACK_WITH_PART_QUESTIONS", CLICKED_BACK_WITH_PART_QUESTIONS: "CLICKED_BACK_WITH_PART_QUESTIONS",
CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE: "CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE", CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE: "CLICKED_BACK_WITH_MULTIPLE_PARTS_TO_CHOOSE",
CLICKED_BACK_WITH_MOLDING_QUESTIONS: "CLICKED_BACK_WITH_MOLDING_QUESTIONS", CLICKED_BACK_WITH_MOLDING_QUESTIONS: "CLICKED_BACK_WITH_MOLDING_QUESTIONS",
CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: "CLICKED_BACK_WITH_CAPABILITY_QUESTIONS", CLICKED_BACK_WITH_CAPABILITY_QUESTIONS: "CLICKED_BACK_WITH_CAPABILITY_QUESTIONS",
// Quote
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS", CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS", CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
}; };

View file

@ -94,7 +94,7 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{ {
scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
@ -102,23 +102,23 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
scenario: navigationScenarios.HAS_PART_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -135,23 +135,23 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
scenario: navigationScenarios.HAS_PART_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -164,31 +164,31 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{ {
scenario: navigationScenarios.CONTINUING_WITH_MULTIPLE_VEHICLES, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_VEHICLES,
destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES, destinationFmgPageValue: fmgPageValues.ADDRESS_VEHICLES,
}, },
{ {
scenario: navigationScenarios.SELECTED_VIN_HAS_MISMATCHED_GLASS, scenario: navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
scenario: navigationScenarios.HAS_PART_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -205,23 +205,23 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE, destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
}, },
{ {
scenario: navigationScenarios.HAS_PART_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_PART_QUESTIONS,
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS, destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -246,7 +246,7 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP, destinationFmgPageValue: fmgPageValues.ADDRESS_LOOKUP,
}, },
{ {
scenario: navigationScenarios.HAS_NO_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -263,19 +263,19 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{ {
scenario: navigationScenarios.HAS_MULTIPLE_PARTS_TO_CHOOSE, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MULTIPLE_PARTS_TO_CHOOSE,
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -300,15 +300,15 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.ESTIMATE, destinationFmgPageValue: fmgPageValues.ESTIMATE,
}, },
{ {
scenario: navigationScenarios.HAS_MOLDING_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_MOLDING_QUESTIONS,
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -333,11 +333,11 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS, destinationFmgPageValue: fmgPageValues.VEHICLE_PARTS,
}, },
{ {
scenario: navigationScenarios.HAS_CAPABILITY_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAPABILITY_QUESTIONS,
destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS, destinationFmgPageValue: fmgPageValues.CAPABILITY_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],
@ -366,7 +366,7 @@ const routingTable = function (store) {
destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS, destinationFmgPageValue: fmgPageValues.MOLDING_QUESTIONS,
}, },
{ {
scenario: navigationScenarios.HAS_NO_MORE_QUESTIONS, scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
destinationFmgPageValue: fmgPageValues.QUOTE, destinationFmgPageValue: fmgPageValues.QUOTE,
}, },
], ],

View file

@ -55,6 +55,9 @@ const getDefaultState = () => {
}, },
lineItems: { lineItems: {
glassParts: null, glassParts: null,
supportingItems: null,
vaps: null,
serverData: null,
}, },
payment: { payment: {
isInsurance: null, isInsurance: null,
@ -145,8 +148,11 @@ export const mutations = {
updateGlassParts(state, partsData) { updateGlassParts(state, partsData) {
state.order.lineItems.glassParts = partsData; state.order.lineItems.glassParts = partsData;
}, },
updateOtherParts(state, partsData) { updateVaps(state, partsData) {
state.order.lineItems.otherParts = partsData; state.order.lineItems.vaps = partsData;
},
updateSupportingItems(state, partsData) {
state.order.lineItems.supportingItems = partsData;
}, },
updatePageData(state, pageData) { updatePageData(state, pageData) {
state.applicationUser.pageData[pageData.page] = pageData.data; state.applicationUser.pageData[pageData.page] = pageData.data;
@ -298,6 +304,12 @@ export const mutations = {
state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null;
state.applicationUser.pageData[fmgPageValues.CAPABILITY_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.CAPABILITY_QUESTIONS] = null;
}, },
resetSupportingItemsState(state) {
state.order.lineItems.supportingItems = null;
},
resetVapsState(state) {
state.order.lineItems.vaps = null;
},
resetState(state) { resetState(state) {
Object.assign(state, getDefaultState()); Object.assign(state, getDefaultState());
}, },
@ -305,13 +317,13 @@ export const mutations = {
state.applicationUser.saveSessionPromise = null; state.applicationUser.saveSessionPromise = null;
}, },
// Misc Mutations // Misc Mutations
updateStateWithOrderInformation(state, orderInformation) { updateStateWithOrderInformation(state, sessionInformation) {
state.order.referralNumber = orderInformation.referralNumber; state.order.referralNumber = sessionInformation.order.referralNumber;
state.order.referralDate = orderInformation.referralDate; state.order.referralDate = sessionInformation.order.referralDate;
state.order.referralCorrelationId = orderInformation.referralCorrelationId; state.order.referralCorrelationId = sessionInformation.order.referralCorrelationId;
state.order.eon = orderInformation.eon; state.order.eon = sessionInformation.order.eon;
if (state.order.vehicle.vin !== orderInformation.vehicle?.vin) { if (state.order.vehicle.vin !== sessionInformation.order.vehicle?.vin) {
state.applicationUser.pageData[fmgPageValues.PART_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.PART_QUESTIONS] = null;
state.applicationUser.pageData[fmgPageValues.VEHICLE_PARTS] = null; state.applicationUser.pageData[fmgPageValues.VEHICLE_PARTS] = null;
state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null; state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null;
@ -319,46 +331,56 @@ export const mutations = {
} }
state.order.vehicle = Object.assign(state.order.vehicle, { state.order.vehicle = Object.assign(state.order.vehicle, {
year: orderInformation.vehicle?.year, year: sessionInformation.order.vehicle?.year,
make: orderInformation.vehicle?.make, make: sessionInformation.order.vehicle?.make,
model: orderInformation.vehicle?.model, model: sessionInformation.order.vehicle?.model,
style: orderInformation.vehicle?.style, style: sessionInformation.order.vehicle?.style,
vin: orderInformation.vehicle?.vin, vin: sessionInformation.order.vehicle?.vin,
carId: orderInformation.vehicle?.carId, carId: sessionInformation.order.vehicle?.carId,
category: orderInformation.vehicle?.category, category: sessionInformation.order.vehicle?.category,
imageUrl: orderInformation.vehicle?.imageUrl, imageUrl: sessionInformation.order.vehicle?.imageUrl,
imageVifNumber: orderInformation.vehicle?.imageVifNumber, imageVifNumber: sessionInformation.order.vehicle?.imageVifNumber,
imageColor: orderInformation.vehicle?.imageVifColor, imageColor: sessionInformation.order.vehicle?.imageVifColor,
registration: { registration: {
firstName: orderInformation.vehicle.registration.firstName, firstName: sessionInformation.order.vehicle.registration.firstName,
lastName: orderInformation.vehicle.registration.lastName, lastName: sessionInformation.order.vehicle.registration.lastName,
address: orderInformation.vehicle.registration.streetAddress, address: sessionInformation.order.vehicle.registration.streetAddress,
city: orderInformation.vehicle.registration.city, city: sessionInformation.order.vehicle.registration.city,
state: orderInformation.vehicle.registration.state, state: sessionInformation.order.vehicle.registration.state,
zipCode: orderInformation.vehicle.registration.zipCode, zipCode: sessionInformation.order.vehicle.registration.zipCode,
licensePlate: orderInformation.vehicle.registration.licensePlateNumber, licensePlate: sessionInformation.order.vehicle.registration.licensePlateNumber,
}, },
}); });
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace; state.order.damage.glassToReplace = sessionInformation.order.damage.glassToReplace;
state.order.damage.isRepair = orderInformation.damage.isRepair; state.order.damage.isRepair = sessionInformation.order.damage.isRepair;
state.order.damage.numberOfChips = orderInformation.damage.numberOfChips; state.order.damage.numberOfChips = sessionInformation.order.damage.numberOfChips;
state.order.lineItems.glassParts = orderInformation.parts; state.order.lineItems.glassParts = sessionInformation.order.lineItems.glassParts;
state.order.accountNumber = orderInformation.accountNumber; state.order.lineItems.supportingItems = sessionInformation.order.lineItems.supportingItems;
(state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress), state.order.lineItems.vaps = sessionInformation.order.lineItems.vaps;
(state.order.serviceLocation.city = orderInformation.serviceLocation.city), state.order.lineItems.serverData = sessionInformation.order.lineItems.serverData;
(state.order.serviceLocation.state = orderInformation.serviceLocation.state), state.order.accountNumber = sessionInformation.order.accountNumber;
(state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode); state.order.providerNumber = sessionInformation.order.providerNumber;
(state.order.serviceLocation.address =
state.order.payment.isInsurance = orderInformation.isInsuranceOrder; sessionInformation.order.serviceLocation.streetAddress),
(state.order.serviceLocation.city = sessionInformation.order.serviceLocation.city),
(state.order.serviceLocation.state = sessionInformation.order.serviceLocation.state),
(state.order.serviceLocation.zipCode =
sessionInformation.order.serviceLocation.zipCode);
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
state.order.payment.insuranceCoverage.isVerified = state.order.payment.insuranceCoverage.isVerified =
orderInformation?.insuranceInfo.coverageVerified; sessionInformation?.order.payment.insuranceCoverage.isVerified;
state.order.payment.insuranceCoverage.coverageStatus = orderInformation?.insuranceInfo.coverageStatus // TODO KO CSR-747 double check this
state.order.payment.insuranceCoverage.coverageStatus = sessionInformation?.insuranceInfo.coverageStatus
state.order.customer.emailAddress = orderInformation.customer.emailAddress; state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress;
state.applicationUser.experiments = orderInformation.experiments; state.order.existingPromoCode = sessionInformation.order.existingPromoCode;
state.applicationUser.experiments = sessionInformation.applicationUser.experiments;
state.applicationUser.crmCustomerId = sessionInformation.applicationUser.crmCustomerId;
state.applicationUser.pageData = sessionInformation.applicationUser.pageData;
state.applicationUser.lastPage = sessionInformation.applicationUser.lastPage;
}, },
updateExperiments(state, experiments) { updateExperiments(state, experiments) {
state.applicationUser.experiments = experiments; state.applicationUser.experiments = experiments;
@ -433,7 +455,11 @@ export const getters = {
"partNumber" "partNumber"
), ),
...getNonFalseValuesOfPropertyInArrayOfObjects( ...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.otherParts, state.order.lineItems.supportingItems,
"partNumber"
),
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.vaps,
"partNumber" "partNumber"
), ),
], ],
@ -443,10 +469,6 @@ export const getters = {
state.order.lineItems.glassParts, state.order.lineItems.glassParts,
"recalibrationType" "recalibrationType"
), ),
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.otherParts,
"recalibrationType"
),
], ],
}; };
}, },
@ -569,21 +591,22 @@ export const actions = {
}, },
// Dependency Actions // Dependency Actions
resetVehicleAndDependencies(context) {
context.commit(storeMutations.RESET_VEHICLE_STATE);
context.commit(storeMutations.RESET_DAMAGE_STATE);
context.commit(storeMutations.RESET_REGISTRATION_STATE);
},
resetDamageAndDependencies(context) { resetDamageAndDependencies(context) {
context.commit(storeMutations.RESET_DAMAGE_STATE); context.commit(storeMutations.RESET_DAMAGE_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.RESET_VAPS_STATE);
}, },
resetRegistrationAndDependencies(context) { resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_STATE); context.commit(storeMutations.RESET_REGISTRATION_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.RESET_VAPS_STATE);
}, },
resetPartsAndDependencies(context) { resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_GLASS_PARTS_STATE); context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.RESET_VAPS_STATE);
}, },
resetState(context) { resetState(context) {
context.commit(storeMutations.RESET_STATE); context.commit(storeMutations.RESET_STATE);
@ -868,6 +891,49 @@ export const actions = {
return response; return response;
}, },
getWipers(context) {
const carId = context.getters.vehicle.carId;
const serviceZipCode = context.getters.order.serviceLocation.zipCode;
return globalMethods
.callHttpClient({
method: endpoints.GetWipers.method,
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`,
})
.catch((error) => {
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
return [];
});
},
getRainDefense(context) {
return globalMethods.callHttpClient({
method: endpoints.GetRainDefense.method,
endpoint: endpoints.GetRainDefense.url,
});
},
getSupportingItems(context) {
const glassPartsArray = context.getters.lineItems.glassParts;
if (!glassPartsArray) {
return [];
}
const carId = context.getters.vehicle.carId;
const isRepair = context.getters.damage.isRepair;
const numberOfChips = context.getters.damage.numberOfChips;
const parentAccountNumber = context.getters.order.accountNumber.toString();
return globalMethods.callHttpClient({
method: endpoints.GetSupportingItems.method,
endpoint: endpoints.GetSupportingItems.url,
payload: {
carId: carId,
serviceType: isRepair ? "Repair" : "Replace",
parentAccountNumber: parentAccountNumber,
parts: glassPartsArray,
numberOfRepairChips: isRepair ? numberOfChips : 0,
},
});
},
getCapabilityQuestions(context, { carId, partNumber }) { getCapabilityQuestions(context, { carId, partNumber }) {
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
method: endpoints.GetCapabilityQuestions.method, method: endpoints.GetCapabilityQuestions.method,
@ -948,6 +1014,9 @@ export const actions = {
}, },
lineItems: { lineItems: {
glassParts: lineItems.glassParts, glassParts: lineItems.glassParts,
supportingItems: lineItems.supportingItems,
vaps: lineItems.vaps,
serverData: lineItems.serverData,
}, },
payment: { payment: {
InsuranceCoverage: { InsuranceCoverage: {
@ -972,21 +1041,16 @@ export const actions = {
}, },
}); });
}, },
loadSession(context, { referralNumber, referralDate, referralCorrelationId, accountNumber }) { loadSession(context, { savedSessionId }) {
return globalMethods return globalMethods
.callHttpClient({ .callHttpClient({
method: endpoints.LoadSession.method, method: endpoints.LoadSession.method,
endpoint: endpoints.LoadSession.url, endpoint:
payload: { endpoints.LoadSession.url + "?savedSessionId=" + savedSessionId?.toString(),
referralNumber: referralNumber?.toString(),
referralDate: referralDate,
referralCorrelationId: referralCorrelationId,
accountNumber: accountNumber?.toString(),
},
}) })
.then((response) => { .then((response) => {
// Flatten location and name properties // Flatten location and name properties
response.data.damage?.glassToReplace?.map((glass) => { response.data.order.damage?.glassToReplace?.map((glass) => {
glass.glassLocation = glass.location; glass.glassLocation = glass.location;
glass.glassName = glass.name; glass.glassName = glass.name;
delete glass.location; delete glass.location;
@ -1204,6 +1268,7 @@ export const actions = {
if (havePartQuestionAnswersChanged) { if (havePartQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
@ -1249,6 +1314,7 @@ export const actions = {
if (haveSelectedVehiclePartsChanged) { if (haveSelectedVehiclePartsChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
@ -1278,6 +1344,7 @@ export const actions = {
if (haveMoldingQuestionAnswersChanged) { if (haveMoldingQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null); context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.CAPABILITY_QUESTIONS, page: fmgPageValues.CAPABILITY_QUESTIONS,
@ -1305,6 +1372,7 @@ export const actions = {
if (haveCapabilityQuestionAnswersChanged) { if (haveCapabilityQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null); context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
} }
//Save new values //Save new values
@ -1313,6 +1381,25 @@ export const actions = {
capabilityQuestionAnswers capabilityQuestionAnswers
); );
}, },
// Quote Page
saveQuotePageSelections(
context,
{ isInsuranceSelected, vapsItemsToAdd, supportingItemsToAdd }
) {
context.commit(storeMutations.RESET_SUPPORTING_ITEMS_STATE);
context.commit(storeMutations.RESET_VAPS_STATE);
context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsuranceSelected);
context.commit(storeMutations.UPDATE_VAPS, vapsItemsToAdd);
context.commit(storeMutations.UPDATE_SUPPORTING_ITEMS, supportingItemsToAdd);
},
// Price order actions
// PLACEHOLDER, WILL CHANGE WHEN PRICING END POINT IS IMPLEMENTED
priceOrderItems(context, availableLineItems) {
availableLineItems.forEach((lineItem) => {
lineItem["price"] = parseFloat((Math.random() * 100).toFixed(2));
});
return availableLineItems;
},
// Misc order actions // Misc order actions
saveServiceLocation(context, serviceLocationInfo) { saveServiceLocation(context, serviceLocationInfo) {
context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo); context.commit(storeMutations.UPDATE_SERVICE_LOCATION, serviceLocationInfo);
@ -1341,6 +1428,30 @@ export const actions = {
saveIsInsurance(context, isInsurance) { saveIsInsurance(context, isInsurance) {
context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance); context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance);
}, },
isVinOptionalVehicle(context) {
switch (context.state.order.vehicle.make.toLowerCase()) {
case "mercedes benz":
case "volkswagen":
case "audi":
case "porsche":
return true;
default:
}
if (
context.state.order.vehicle.make.toLowerCase() === "ford" &&
context.state.order.vehicle.year >= 2018
)
return true;
if (
context.state.order.vehicle.make.toLowerCase() === "bmw" &&
context.state.order.vehicle.year <= 2017
)
return true;
return false;
},
}; };
export default createStore({ export default createStore({
@ -1399,7 +1510,13 @@ function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
} }
function convertGlassPieceNamingForApi(glassArray) { function convertGlassPieceNamingForApi(glassArray) {
if (!glassArray) return []; if (!glassArray || glassArray.length === 0) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassArray[0].location !== undefined) {
return glassArray;
}
const converted = []; const converted = [];
glassArray.forEach((glass) => { glassArray.forEach((glass) => {
converted.push({ converted.push({

View file

@ -203,34 +203,65 @@ describe("Mutations", () => {
expect(storeState.applicationUser.pageData["vehicle-year"]).toEqual({}); expect(storeState.applicationUser.pageData["vehicle-year"]).toEqual({});
}); });
it("updateStateWithOrderInformation, should set order information in state", () => { it("updateStateWithSessonInformation, should set session information in state", () => {
// Arrange // Arrange
const storeState = state; const storeState = state;
// Act // Act
mutations.updateStateWithOrderInformation(storeState, { mutations.updateStateWithOrderInformation(storeState, {
referralNumber: 123, order: {
referralDate: new Date().toUTCString(), referralNumber: 123,
referralCorrelationId: "xxx-xxx-xxx", referralDate: new Date().toUTCString(),
vehicle: { referralCorrelationId: "xxx-xxx-xxx",
year: "2019", vehicle: {
make: "Acura", year: "2019",
model: "ILX", make: "Acura",
style: "4 DOOR SEDAN", model: "ILX",
carId: "C0000001", style: "4 DOOR SEDAN",
category: "CAR", carId: "C0000001",
registration: {}, category: "CAR",
registration: {},
},
damage: {
glassToReplace: ["Windshield"],
isRepair: false,
numberOfChips: 0,
},
lineItems: {
glassParts: null,
},
payment: {
insuranceCoverage: {
isVerified: false,
},
isInsurance: false,
},
parts: [],
accountNumber: "123456789",
insuranceInfo: {},
serviceLocation: {},
customer: {},
}, },
damage: { applicationUser: {
glassToReplace: ["Windshield"], experiments: [
isRepair: false, {
numberOfChips: 0, universeName: "Concept Funnel Test With Rules",
universeId: 463,
testName: "Concept Dev Test",
testId: 392,
variationName: "Concept Test Variation",
variationId: 1133,
isActive: false,
isExposed: true,
userPartitionNumber: 84,
assignmentId: 12211739,
settings: {
someKey: "false",
sampleSetting: "Hi, my name is Vidya",
},
},
],
}, },
parts: [],
accountNumber: "123456789",
insuranceInfo: {},
serviceLocation: {},
customer: {},
}); });
// Assert // Assert
@ -445,21 +476,6 @@ describe("Actions", () => {
expect(response.data).toEqual("43201"); expect(response.data).toEqual("43201");
}); });
it("resetVehicleAndDependencies action", async () => {
// Arrange
const context = state;
const commit = jest.fn();
context.commit = commit;
// Act
await actions.resetVehicleAndDependencies(context);
expect(commit).toBeCalledWith(storeMutations.RESET_VEHICLE_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_DAMAGE_STATE);
expect(commit).toBeCalledWith(storeMutations.RESET_REGISTRATION_STATE);
});
it("resetDamageAndDependencies action", async () => { it("resetDamageAndDependencies action", async () => {
// Arrange // Arrange
const context = state; const context = state;
@ -631,7 +647,7 @@ describe("Actions", () => {
const context = state; const context = state;
globalMethods.callHttpClient.mockImplementation(() => { globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { referralNumber: 123 } }); return Promise.resolve({ data: { referralNumber: 123, order: {} } });
}); });
const commit = jest.fn(); const commit = jest.fn();
@ -640,15 +656,14 @@ describe("Actions", () => {
// Act // Act
const response = await actions.loadSession(context, { const response = await actions.loadSession(context, {
referralNumber: "123", savedSessionId: "",
referralDate: new Date().toUTCString(),
referralCorrelationId: "xxx-xxx-xxx",
}); });
// Assert // Assert
expect(response.data).toEqual({ referralNumber: 123 }); expect(response.data).toEqual({ referralNumber: 123, order: {} });
expect(commit).toBeCalledWith(storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, { expect(commit).toBeCalledWith(storeMutations.UPDATE_STATE_WITH_ORDER_INFORMATION, {
referralNumber: 123, referralNumber: 123,
order: {},
}); });
}); });
@ -657,7 +672,7 @@ describe("Actions", () => {
const context = state; const context = state;
globalMethods.callHttpClient.mockImplementation(() => { globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { eon: "123" } }); return Promise.resolve({ data: { eon: "123", order: {} } });
}); });
context.commit = jest.fn(); context.commit = jest.fn();
@ -667,9 +682,7 @@ describe("Actions", () => {
// Act // Act
const response = await actions.loadSession(context, { const response = await actions.loadSession(context, {
referralNumber: "123", savedSessionId: "",
referralDate: new Date().toUTCString(),
referralCorrelationId: "xxx-xxx-xxx",
}); });
// Assert // Assert
@ -682,7 +695,7 @@ describe("Actions", () => {
const context = state; const context = state;
globalMethods.callHttpClient.mockImplementation(() => { globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { eon: "123" } }); return Promise.resolve({ data: { eon: "123", order: {} } });
}); });
context.commit = jest.fn(); context.commit = jest.fn();
@ -694,9 +707,7 @@ describe("Actions", () => {
// Act // Act
const response = await actions.loadSession(context, { const response = await actions.loadSession(context, {
referralNumber: "123", savedSessionId: "",
referralDate: new Date().toUTCString(),
referralCorrelationId: "xxx-xxx-xxx",
}); });
// Assert // Assert
@ -2383,7 +2394,8 @@ describe("Getters", () => {
funnelParentAccountNumber: "999999", funnelParentAccountNumber: "999999",
funnelIsCoverageVerified: true, funnelIsCoverageVerified: true,
funnelGlassParts: null, funnelGlassParts: null,
funnelOtherParts: null, funnelSupportingItems: null,
funnelVaps: null,
funnelGlassToReplace: null, funnelGlassToReplace: null,
}; };
@ -2406,7 +2418,8 @@ describe("Getters", () => {
mockStateValues.funnelIsCoverageVerified mockStateValues.funnelIsCoverageVerified
); );
mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts); mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts);
mutations.updateOtherParts(storeState, mockStateValues.funnelOtherParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems);
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.funnelGlassToReplace); mutations.updateGlassToReplace(storeState, mockStateValues.funnelGlassToReplace);
//Assert //Assert
@ -2474,7 +2487,8 @@ describe("Getters", () => {
mockStateValues.funnelIsCoverageVerified mockStateValues.funnelIsCoverageVerified
); );
mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts); mutations.updateGlassParts(storeState, mockStateValues.funnelGlassParts);
mutations.updateOtherParts(storeState, mockStateValues.funnelOtherParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems);
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.funnelGlassToReplace); mutations.updateGlassToReplace(storeState, mockStateValues.funnelGlassToReplace);
//Assert //Assert
@ -2552,7 +2566,8 @@ describe("Getters", () => {
mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber);
mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified);
mutations.updateGlassParts(storeState, mockStateValues.glassParts); mutations.updateGlassParts(storeState, mockStateValues.glassParts);
mutations.updateOtherParts(storeState, mockStateValues.otherParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems);
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace);
//Assert //Assert
@ -2656,7 +2671,8 @@ describe("Getters", () => {
mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber);
mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified);
mutations.updateGlassParts(storeState, mockStateValues.glassParts); mutations.updateGlassParts(storeState, mockStateValues.glassParts);
mutations.updateOtherParts(storeState, mockStateValues.otherParts); mutations.updateSupportingItems(storeState, mockStateValues.funnelSupportingItems);
mutations.updateVaps(storeState, mockStateValues.funnelVaps);
mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace);
//Assert //Assert
@ -2685,3 +2701,33 @@ describe("Getters", () => {
}); });
}); });
}); });
describe("isVinOptionalVehicle", () => {
const testVehicles = [
["2017", "acura", false],
["2017", "ford", false],
["2018", "ford", true],
["2018", "bmw", false],
["2017", "bmw", true],
["2016", "bmw", true],
["2016", "mercedes benz", true],
["2016", "volkswagen", true],
["2016", "audi", true],
["2016", "porsche", true],
];
test.each(testVehicles)(
"%s %s should skip vin lookup is %s",
async (year, make, expectedVinSkip) => {
const context = state;
context.state = {
order: {
vehicle: { year: year, make: make },
},
};
var vinOptionalResult = actions.isVinOptionalVehicle(context);
expect(vinOptionalResult).toEqual(expectedVinSkip);
}
);
});

View file

@ -1,12 +1,10 @@
html { html {
.has-error { .has-error {
// START HOVER
&.list-button-horizontal, &.list-button-horizontal,
&.list-button, &.list-button,
&.list-button.list-group,
&.list-card { &.list-card {
border: 1px solid $red;
position: relative; position: relative;
z-index: 4;
.button-content { .button-content {
border: none; border: none;
@ -16,6 +14,30 @@ html {
@include box-shadow-hover($red-200); @include box-shadow-hover($red-200);
z-index: 5; z-index: 5;
} }
input[type="radio"]:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $red;
}
.list-button-content,
.list-button-horizontal-content {
border: 1px solid $red;
}
input[type="radio"]:focus + .list-button-horizontal-content {
z-index: 5;
box-shadow: 0 0 0 2.5px $red;
}
input[type="radio"]:hover + .list-button-content,
input[type="radio"]:hover + .list-button-horizontal-content {
z-index: 5;
box-shadow: none;
}
}
&.list-card {
border: 1px solid $red;
} }
&.ui-radio { &.ui-radio {
@ -26,7 +48,11 @@ html {
} }
} }
} }
// END HOVER .form-check-input {
&:focus {
box-shadow: 0 0 0 2.5px $red;
}
}
&.list-button, &.list-button,
&.list-card { &.list-card {
@ -159,13 +185,13 @@ html {
pointer-events: all; pointer-events: all;
font-weight: $font-weight-normal; font-weight: $font-weight-normal;
} }
&.btn.btn-primary:hover { &.btn.btn-primary:hover,
&.btn.btn-primary:focus {
background: $gray-200; background: $gray-200;
box-shadow: none; box-shadow: none;
} }
&.btn.btn-primary:focus,
&.btn.btn-primary:focus-visible { &.btn.btn-primary:focus-visible {
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700; box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $gray-700;
} }
} }
} }

View file

@ -163,6 +163,9 @@ $spacers: (
/* 48px */ /* 48px */
); );
//Enable negative spacing (does NOT work on padding)
$enable-negative-margins: true;
//Grid breakpoints //Grid breakpoints
$grid-breakpoints: ( $grid-breakpoints: (
xs: 0, xs: 0,

View file

@ -1,7 +1,7 @@
<template> <template>
<button <button
:aria-disabled="isDisabled" :aria-disabled="isDisabled"
class="btn d-flex align-items-center py-3 px-4 delay" class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
:class="[ :class="[
isPrimary ? 'btn-primary' : 'btn-secondary', isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '', isFloat ? 'float-end' : '',
@ -11,7 +11,7 @@
<span class="m-0">{{ this.buttonText }}</span> <span class="m-0">{{ this.buttonText }}</span>
<loader <loader
class="ms-2" class="ms-2"
v-if="isLoaderDisplayed" v-if="isLoaderDisplayed && !suppressLoader"
v-bind:class="[this.loaderColor, this.loaderPosition]" /> v-bind:class="[this.loaderColor, this.loaderPosition]" />
</button> </button>
</template> </template>
@ -28,6 +28,7 @@ export default {
loaderColor: String, loaderColor: String,
loaderPosition: String, loaderPosition: String,
isFloat: Boolean, isFloat: Boolean,
suppressLoader: Boolean,
}, },
data() { data() {
return { return {
@ -50,6 +51,12 @@ export default {
this.$emit("click-event"); this.$emit("click-event");
} }
}, },
resetButtonStyle() {
this.isLoaderDisplayed = false;
},
},
mounted() {
this.isLoaderDisplayed = false;
}, },
components: { components: {
loader, loader,

View file

@ -60,56 +60,54 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.list-group { .loader {
.loader { position: absolute;
position: absolute; }
} .list-button {
&.list-button { outline: none;
outline: none; input[type="radio"],
input[type="radio"], input[type="checkbox"] {
input[type="checkbox"] { position: static; //override bootstrap
position: static; //override bootstrap
&:focus-visible + .list-button-content { &:focus-visible + .list-button-content {
box-shadow: 0 0 0 2.5px $blue; box-shadow: 0 0 0 2.5px $blue;
} }
&:focus + .list-button-content { &:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue; box-shadow: 0 0 0 2.5px $blue;
} }
&:checked + .list-button-content { &:checked + .list-button-content {
color: $black; color: $black;
font-weight: 500; font-weight: 500;
background: $blue-100; background: $blue-100;
box-shadow: 0 0 0 1px $blue; box-shadow: 0 0 0 1px $blue;
} }
&:checked:focus + .list-button-content { &:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue; box-shadow: 0 0 0 2.5px $blue;
} }
&:checked + .list-button-content p, &:checked + .list-button-content p,
&:checked + .list-button-content span { &:checked + .list-button-content span {
font-weight: 500; font-weight: 500;
} }
&:checked + .list-button-content span:nth-child(2) { &:checked + .list-button-content span:nth-child(2) {
font-weight: 400; font-weight: 400;
color: $gray-600; color: $gray-600;
}
} }
} }
.list-button-content { }
color: $gray-600; .list-button-content {
position: relative; color: $gray-600;
background: $white; position: relative;
transition: all 150ms linear; background: $white;
border-radius: $border-radius-lg; transition: all 150ms linear;
border: 1px solid $gray-500; border-radius: $border-radius-lg;
width: 100%; border: 1px solid $gray-500;
outline: none; width: 100%;
outline: none;
span { span {
&.small { &.small {
font-size: 0.75rem; font-size: 0.75rem;
color: $gray-550; color: $gray-550;
}
} }
} }
} }

View file

@ -1,19 +1,21 @@
<template> <template>
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href">{{ <a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href"
text >{{ text }}<slot name="after-text"></slot
}}</a> ></a>
<a <a
v-else-if="linkType === 'footer'" v-else-if="linkType === 'footer'"
@click="handleClick" @click="handleClick"
class="footer-link" class="footer-link"
:href="href" :href="href"
target="_blank" target="_blank"
>{{ text }}</a >{{ text }}<slot name="after-text"></slot
> ></a>
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">{{ <a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href"
text >{{ text }}<slot name="after-text"></slot
}}</a> ></a>
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">{{ text }}</a> <a v-else-if="linkType === 'text'" @click="handleClick" :href="href"
>{{ text }}<slot name="after-text"></slot
></a>
</template> </template>
<script> <script>
@ -48,6 +50,7 @@ a {
line-height: 26px; line-height: 26px;
padding: 0 0 4px 0; padding: 0 0 4px 0;
font-weight: 500; font-weight: 500;
max-width: fit-content;
&:hover { &:hover {
color: $blue-700; color: $blue-700;
} }
@ -61,6 +64,7 @@ a {
color: $black; color: $black;
line-height: 26px; line-height: 26px;
white-space: nowrap; white-space: nowrap;
align-items: center;
} }
&.footer-link { &.footer-link {
color: $gray-600; color: $gray-600;