Merge remote-tracking branch 'origin/develop' into feature/CSR-731
This commit is contained in:
commit
c114c75e02
31 changed files with 1638 additions and 1435 deletions
|
|
@ -23,4 +23,5 @@ export default {
|
|||
@import "@/styles/common-typography-styles.scss";
|
||||
@import "@/styles/common-error-styles.scss";
|
||||
@import "@/styles/common-animations.scss";
|
||||
@import "@/styles/shared-input-button-styles.scss";
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ describe("baseInputButton.vue", () => {
|
|||
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
|
||||
});
|
||||
|
||||
test("focus and click enter on a checkbox => update:modelValue is emitted with correct value", async () => {
|
||||
test("focus and click enter on a checkbox => nothing should happen", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
|
|
@ -243,9 +243,10 @@ describe("baseInputButton.vue", () => {
|
|||
|
||||
// Act
|
||||
await input.trigger("keypress", { key: "enter" });
|
||||
console.log(wrapper.emitted()["update:modelValue"]);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["Hi"]);
|
||||
expect(wrapper.emitted()["update:modelValue"]).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -319,7 +320,7 @@ describe("baseInputButton.vue", () => {
|
|||
expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("eventType === eventTypes.ENTER => call handleClick and handlePushClickEventToGACheck", () => {
|
||||
test("eventType === eventTypes.ENTER => do nothing", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
|
|
@ -336,11 +337,9 @@ describe("baseInputButton.vue", () => {
|
|||
wrapper.vm.handleEventAction("enter", { myEvent: "test" });
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.handleClick).toHaveBeenCalledWith({
|
||||
myEvent: "test",
|
||||
});
|
||||
expect(wrapper.vm.handlePushClickEventToGACheck).toHaveBeenCalledWith("click");
|
||||
expect(wrapper.vm.handleClick).not.toHaveBeenCalled();
|
||||
expect(wrapper.vm.handleSelectionChange).not.toHaveBeenCalled();
|
||||
expect(wrapper.vm.handlePushClickEventToGACheck).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("eventType === eventTypes.CHANGE => call handleClick and handlePushClickEventToGACheck", () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<label
|
||||
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
|
||||
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0, selected: isChecked }]"
|
||||
:for="buttonId"
|
||||
@focusin="handleFocus"
|
||||
@focusout="handleBlur"
|
||||
|
|
@ -53,7 +53,6 @@ export default {
|
|||
handleEventAction(eventType, e) {
|
||||
if (this.isMultiSelect) {
|
||||
switch (eventType) {
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.CHANGE:
|
||||
this.handleClick(e);
|
||||
this.handlePushClickEventToGACheck(this.eventTypes.CLICK);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
class="dropdown-question"
|
||||
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
|
||||
<div class="dropdown-question" :class="errorMessage || hasError ? 'has-error' : ''">
|
||||
<label
|
||||
:for="inputId"
|
||||
:aria-label="questionText"
|
||||
|
|
@ -43,6 +41,7 @@ export default {
|
|||
disableAutoFill: Boolean,
|
||||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
hasError: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const propsClone = Object.assign({}, props);
|
||||
|
|
|
|||
|
|
@ -13,19 +13,19 @@
|
|||
<div class="col">
|
||||
<div class="text-container slide">
|
||||
<p>
|
||||
Assessing your damage
|
||||
Finding shops near you
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Finding your glass
|
||||
Looking for dates
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Generating your quote
|
||||
Searching for times
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
|
|
|
|||
|
|
@ -33,14 +33,6 @@ describe("modal.vue", () => {
|
|||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
|
||||
});
|
||||
|
||||
it("Should display footer text when FooterText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(Modal, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["FooterText"]));
|
||||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
|
|
|
|||
|
|
@ -16,16 +16,19 @@
|
|||
aria-label="Close"></button>
|
||||
</div>
|
||||
<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>
|
||||
<p class="fw-bold mb-2 subheader-text" v-html="this.ModalSubheadertext"></p>
|
||||
<p class="mb-0" v-html="this.ModalBodyText"></p>
|
||||
</div>
|
||||
<div class="modal-footer px-5 py-4">
|
||||
<button
|
||||
class="btn btn-primary w-100"
|
||||
data-bs-dismiss="modal"
|
||||
v-html="this.ModalCloseButtonText"></button>
|
||||
<buttonMain
|
||||
class="w-100"
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="ModalCloseButtonText"
|
||||
@click-event="buttonClick"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -33,6 +36,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
props: {
|
||||
|
|
@ -55,6 +60,9 @@ export default {
|
|||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonMain,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -104,7 +112,6 @@ export default {
|
|||
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
|
||||
button {
|
||||
margin: 0;
|
||||
height: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
:class="q.questionSequence === currentQuestionNum && 'current-question'"
|
||||
:questionText="q.questionText"
|
||||
:answers="q.answers"
|
||||
:groupName="`question-${glassIndex}-${q.questionSequence}`"
|
||||
:groupName="`question-${index}-${q.questionSequence}`"
|
||||
:modelValue="q.answerSelected"
|
||||
@update:modelValue="handleAnswer(q, $event)"
|
||||
isRequired
|
||||
|
|
@ -31,8 +31,9 @@ export default {
|
|||
props: {
|
||||
questionData: Object,
|
||||
validationRules: String,
|
||||
modelValue: Array,
|
||||
glassIndex: Number,
|
||||
modelValue: Object,
|
||||
index: Number,
|
||||
answerKey: String,
|
||||
},
|
||||
async created() {
|
||||
// do a test validation check upon create to prevent out of sync / incorrect valid states
|
||||
|
|
@ -65,7 +66,7 @@ export default {
|
|||
}),
|
||||
answerSelected: q.answerSelected || "",
|
||||
};
|
||||
if (!q.suppressQuestion) {
|
||||
if (!q.suppressThisQuestion) {
|
||||
this.questions.push(question);
|
||||
}
|
||||
});
|
||||
|
|
@ -81,7 +82,6 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
handleAnswer(question, returnedAnswer) {
|
||||
question.answerSelected = returnedAnswer;
|
||||
/*
|
||||
returnedAnswer example format:
|
||||
{
|
||||
|
|
@ -90,6 +90,8 @@ export default {
|
|||
"buttonId": "Driver-Front-1-1|answer|DD11132|Yes"
|
||||
}
|
||||
*/
|
||||
|
||||
question.answerSelected = returnedAnswer;
|
||||
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer);
|
||||
|
||||
if (isQuestionChainComplete) {
|
||||
|
|
@ -153,7 +155,7 @@ export default {
|
|||
return {
|
||||
answerResult: questionAnswer,
|
||||
answeredQuestions: answeredQuestions,
|
||||
glassIndex: this.glassIndex,
|
||||
index: this.index,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div :class="`page-container-grouped-styles questions-page`">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||
:questionData="questionsDatum.questions"
|
||||
:index="i"
|
||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||
:answerKey="questionsDatum.answerKey"
|
||||
:validationRules="validationRules" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isMetaValid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="handleForwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/common-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||
|
||||
export default {
|
||||
name: "questions-page",
|
||||
props: {
|
||||
isMetaValid: Boolean,
|
||||
alertFewMoreQuestionsHeader: String,
|
||||
alertFewMoreQuestionsCopy: String,
|
||||
questionsData: Array,
|
||||
validationRules: String,
|
||||
modelValue: Array,
|
||||
index: Number,
|
||||
},
|
||||
computed: {
|
||||
selectedAnswers: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showThisQuestionChain(glass, i) {
|
||||
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
||||
return false;
|
||||
} // return false if no questions or if suppressed
|
||||
return this.index === i || glass.answerData?.answerResult?.length > 0;
|
||||
},
|
||||
handleForwardButtonAction() {
|
||||
this.$emit("forwardButtonAction");
|
||||
},
|
||||
handleBackButtonAction() {
|
||||
this.$emit("backButtonAction");
|
||||
},
|
||||
showLoadingModal() {
|
||||
this.$refs.loadingModal.showModal();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.questions-page {
|
||||
.question-text {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -33,6 +33,46 @@ describe("textboxQuestion.vue", () => {
|
|||
expect(input.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return rounded-pill class", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
global: {
|
||||
directives: {
|
||||
maska: maska,
|
||||
},
|
||||
},
|
||||
mixins: [mockMixin],
|
||||
propsData: {
|
||||
cornerStyle: "rounded",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("input");
|
||||
|
||||
expect(paragraph.attributes("class")).toContain("rounded-pill");
|
||||
});
|
||||
|
||||
it.only("Should return form-control class", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
global: {
|
||||
directives: {
|
||||
maska: maska,
|
||||
},
|
||||
},
|
||||
mixins: [mockMixin],
|
||||
propsData: {
|
||||
questionAlignment: "text",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("input");
|
||||
|
||||
expect(paragraph.attributes("class")).toContain("form-control");
|
||||
});
|
||||
|
||||
it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
|
|
|
|||
|
|
@ -5,25 +5,33 @@
|
|||
:aria-label="questionText"
|
||||
class="form-label"
|
||||
v-html="labelText"></label>
|
||||
<input
|
||||
v-model.trim="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
class="form-control"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:aria-label="questionText"
|
||||
:class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']"
|
||||
:validationRules="validationRules"
|
||||
@change="handleChange"
|
||||
@blur="handleChange"
|
||||
:maxlength="maxLength ? maxLength : '999'"
|
||||
@focus="$emit('focus', $event.target.value)" />
|
||||
<div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']">
|
||||
<input
|
||||
class="form-control"
|
||||
v-model.trim="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:aria-label="questionText"
|
||||
:class="[
|
||||
hasIcon ? 'has-icon' : '',
|
||||
iconRight ? 'icon-right' : '',
|
||||
questionAlignment === 'center' ? 'text-center' : '',
|
||||
cornerStyle === 'rounded' ? 'rounded-pill' : '',
|
||||
]"
|
||||
:validationRules="validationRules"
|
||||
@change="handleChange"
|
||||
@blur="handleChange"
|
||||
:maxlength="maxLength ? maxLength : '999'"
|
||||
@focus="$emit('focus', $event.target.value)" />
|
||||
<button v-if="includeSearchIcon" type="submit" aria-label="Search button" />
|
||||
</div>
|
||||
<div v-show="errorMessage" class="row my-2 form-test-error">
|
||||
<span class="d-inline-flex mt-0" role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
|
@ -58,6 +66,9 @@ export default {
|
|||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
maxLength: String,
|
||||
questionAlignment: String, // Left or center. Left is default.
|
||||
cornerStyle: String, // Rounded or square. Square is default.
|
||||
includeSearchIcon: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const propsClone = Object.assign({}, props);
|
||||
|
|
@ -148,6 +159,30 @@ export default {
|
|||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
&.has-search-icon {
|
||||
input[type="text"] {
|
||||
border-radius: 50rem;
|
||||
}
|
||||
button[type="submit"] {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border-radius: 0 50rem 50rem 0;
|
||||
background-color: $blue-100;
|
||||
width: 2.75rem;
|
||||
height: 100%;
|
||||
border: 1px solid $gray-500;
|
||||
border-left: none;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ const endpoints = {
|
|||
},
|
||||
LoadSession: {
|
||||
url: "/order/api/v1/order/load-session",
|
||||
method: "POST",
|
||||
method: "GET",
|
||||
},
|
||||
ValidateZip: {
|
||||
url: "/location/api/v1/location/zip",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export function updateOrCreateFunnelCookie() {
|
|||
ReferralParentAccountNumber: store.getters.order.accountNumber,
|
||||
HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
|
||||
SuppressConceptFunnel: shouldSuppressConceptFunnel,
|
||||
SavedSessionId: store.getters.applicationUser.savedSessionId,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,8 @@ import { storeMutations } from "@/constants/store-mutations";
|
|||
export async function loadSessionIfPresent() {
|
||||
const funnelCookie = getFunnelCookie();
|
||||
|
||||
// Do nothing if there is no cookie, correlation id, or referral number.
|
||||
if (
|
||||
funnelCookie == null ||
|
||||
funnelCookie.ReferralCorrelationId == null ||
|
||||
!funnelCookie.ReferralNumber
|
||||
) {
|
||||
// Do nothing if there is no cookie or session to use for loading.
|
||||
if (funnelCookie == null || funnelCookie.SavedSessionId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -33,15 +29,8 @@ export async function loadSessionIfPresent() {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Load referral if there is a cookie, and it doesn't indicate it needs a state reset.
|
||||
return (
|
||||
await loadSession(
|
||||
funnelCookie.ReferralNumber,
|
||||
funnelCookie.ReferralDate,
|
||||
funnelCookie.ReferralCorrelationId,
|
||||
funnelCookie.ReferralParentAccountNumber
|
||||
)
|
||||
).data;
|
||||
// Loads session including referral if there is a cookie, and it doesn't indicate it needs a state reset.
|
||||
return (await loadSession(funnelCookie.SavedSessionId)).data;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -72,16 +61,13 @@ export async function saveSession() {
|
|||
Calls API to load session given the referral number, referralDate, and referralCorrelationId
|
||||
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 store.getters.applicationUser.saveSessionPromise;
|
||||
const response = await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.LOAD_SESSION,
|
||||
{
|
||||
referralNumber: referralNumber.toString(),
|
||||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId,
|
||||
accountNumber: accountNumber?.toString(),
|
||||
savedSessionId: savedSessionId?.toString(),
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ describe("loadSessionIfPresent", () => {
|
|||
ShouldResetState: testShouldResetState,
|
||||
ReferralCorrelationId: "xxx",
|
||||
ReferralNumber: "12345",
|
||||
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
|
||||
};
|
||||
|
||||
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${JSON.stringify(
|
||||
|
|
@ -46,6 +47,7 @@ describe("loadSessionIfPresent", () => {
|
|||
ShouldResetState: true,
|
||||
ReferralCorrelationId: "xxx-xxx-xxx",
|
||||
ReferralNumber: "12345",
|
||||
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
|
|
@ -103,6 +105,7 @@ describe("loadSessionIfPresent", () => {
|
|||
ReferralNumber: 123456,
|
||||
ReferralCorrelationId: "yyy-yyy-yyyy",
|
||||
ReferralDate: new Date(),
|
||||
SavedSessionId: "aa115a05-e268-4eb4-a095-1d54b44f4a99",
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ export default {
|
|||
},
|
||||
selectedVehicle() {
|
||||
// this computed is only needed for the computed differentVehicleAlertBody text above
|
||||
return this.vehicles.find(
|
||||
({ vin }) => vin === this.selectedVehicleVin[this.selectedVehicleVin.length - 1]
|
||||
);
|
||||
return this.vehicles.find(({ vin }) => vin === this.selectedVehicleVin);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -1,61 +1,32 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles capability-questions">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFewMoreQuestionsHeader"
|
||||
:manualCopy="AlertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(glass, i) in capabilityQuestionsData" :key="glass.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[glass.glassLocation + '-' + glass.glassName]"
|
||||
:questionData="glass.capabilityQuestions"
|
||||
:glassIndex="i"
|
||||
v-if="showThisQuestionChain(glass, i)"
|
||||
validationRules="questions-required" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<questions-page-layout
|
||||
ref="questionsPageLayout"
|
||||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
:validationRules="questions - required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@backButtonAction="backButtonAction"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/common-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout";
|
||||
|
||||
// Supporting Files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
|
@ -84,10 +55,9 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
capabilityQuestionsData: store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
|
||||
.partsOrQuestions,
|
||||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentQuestionChainIndex: 0,
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -97,14 +67,6 @@ export default {
|
|||
AlertFewMoreQuestionsCopy() {
|
||||
return this.getCmsContent("AdditionalPartsQuestionsAlert", "BodyText");
|
||||
},
|
||||
windshieldPart() {
|
||||
return this.pageData.partsOrQuestions.find(
|
||||
(x) => x.glassLocation === damageLocationsSelected.WINDSHIELD
|
||||
);
|
||||
},
|
||||
windshieldPartInfo() {
|
||||
return this.windshieldPart.parts[0];
|
||||
},
|
||||
pageData() {
|
||||
return this.$store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
},
|
||||
|
|
@ -113,30 +75,32 @@ export default {
|
|||
// are there alreadyAnsweredQuestions?
|
||||
const alreadyAnsweredQuestions = store.getters.damage.capabilityQuestionAnswers;
|
||||
|
||||
this.capabilityQuestionsData = this.pageData.partsOrQuestions
|
||||
this.questionsData = this.pageData.partsOrQuestions
|
||||
.filter((x) => x.capabilityQuestions)
|
||||
.map((glass, i) => {
|
||||
.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.key] = [];
|
||||
// pass in: glass, i, alreadyAnsweredQuestions
|
||||
return this.setupInitialData(glass, i, alreadyAnsweredQuestions);
|
||||
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: {
|
||||
showThisQuestionChain(glass, i) {
|
||||
if (
|
||||
!glass.capabilityQuestions ||
|
||||
glass.capabilityQuestions?.length < 1 ||
|
||||
glass.isSuppressedPart
|
||||
) {
|
||||
return false;
|
||||
} // return false if no capabilityQuestions or if suppressed
|
||||
return (
|
||||
this.currentQuestionChainIndex === i || glass.answerData?.answerResult?.length > 0
|
||||
);
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
const capabilityQuestionsPageData = store.getters.pageData(
|
||||
fmgPageValues.CAPABILITY_QUESTIONS
|
||||
|
|
@ -146,10 +110,17 @@ export default {
|
|||
);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const capabilityQuestionsAnswersArray = this.capabilityQuestionsData.map((glass) => {
|
||||
const selectedAnswerResult2 = this.getCorrespondingAnswerResult2(
|
||||
glass.answerData.answerResult
|
||||
);
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
// get answerResult2 of returned answer
|
||||
let selectedAnswerResult2;
|
||||
glass.questions.forEach((q) => {
|
||||
const idx = q.answers.findIndex(
|
||||
(a) => a.answerResult === glass.answerData.answerResult
|
||||
);
|
||||
if (idx !== -1) {
|
||||
selectedAnswerResult2 = q.answers[idx].answerResult2;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
glassLocation: glass.glassLocation,
|
||||
|
|
@ -163,361 +134,39 @@ export default {
|
|||
});
|
||||
|
||||
// clear out answerData for future page loads; must occur prior to store save
|
||||
this.capabilityQuestionsData.forEach((glass) => {
|
||||
this.questionsData.forEach((glass) => {
|
||||
glass.answerData = {};
|
||||
});
|
||||
|
||||
// save to vuex store as order.damage.capabilityQuestionAnswers (array)
|
||||
await this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_CAPABILITY_QUESTION_ANSWERS,
|
||||
capabilityQuestionsAnswersArray,
|
||||
questionAnswersArray,
|
||||
false
|
||||
);
|
||||
|
||||
// get parts from the capabilityQuestionAnswers
|
||||
let partsOrQuestions = this.pageData.partsOrQuestions;
|
||||
for (let answer of capabilityQuestionsAnswersArray) {
|
||||
const correspondingPart = partsOrQuestions.find(
|
||||
(partOrQuestion) => partOrQuestion.glassLocation === answer.glassLocation
|
||||
);
|
||||
const partsOrQuestions = this.pageData.partsOrQuestions;
|
||||
for (let answer of questionAnswersArray) {
|
||||
const partFromCapabilityQuestionAnswer = (
|
||||
await this.dispatchStoreAction(
|
||||
storeActions.GET_PART_FROM_CAPABILITY_QUESTION_ANSWER,
|
||||
this.storeActions.GET_PART_FROM_CAPABILITY_QUESTION_ANSWER,
|
||||
answer.glassLocation,
|
||||
false
|
||||
)
|
||||
).data;
|
||||
|
||||
partsOrQuestions.find(
|
||||
(partOrQuestion) => partOrQuestion.location === answer.location
|
||||
(partOrQuestion) => partOrQuestion.glassLocation === answer.glassLocation
|
||||
).parts = partFromCapabilityQuestionAnswer;
|
||||
}
|
||||
|
||||
this.navigateForward(partsOrQuestions);
|
||||
},
|
||||
handleAnswerUpdates(answer) {
|
||||
// only runs when all questions in a question-chain have been answered
|
||||
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
|
||||
// (does not get run for each invididual question's answer, only when
|
||||
// all relevant questions for the current part have been answered)
|
||||
|
||||
/*
|
||||
answer example format:
|
||||
{
|
||||
"answerResult": "DD11132",
|
||||
"answeredQuestions": [
|
||||
{
|
||||
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||
"selectedAnswerText": "Yes",
|
||||
"questionNum": 1,
|
||||
}
|
||||
],
|
||||
"glassIndex": 0
|
||||
}
|
||||
*/
|
||||
|
||||
// collect a list of the answered questions' numbers, needed later below
|
||||
const answeredQuestionIndexes = [];
|
||||
const foundDuplicateQuestions = [];
|
||||
|
||||
// if user has answered a question differently than anything that was preloaded,
|
||||
// we need to clear out any preloaded answers
|
||||
this.selectedAnswers = {};
|
||||
|
||||
// loop through every answered question on the currently answered glass part
|
||||
answer.answeredQuestions?.forEach((aq) => {
|
||||
// keep track of this question number
|
||||
answeredQuestionIndexes.push(aq.questionNum);
|
||||
|
||||
const answeredQuestionText = aq.questionText.toUpperCase();
|
||||
const answeredQuestionAnswer = aq.selectedAnswerText.toUpperCase();
|
||||
|
||||
// HANDLE DUPLICATE QUESTIONS
|
||||
|
||||
// loop through all glass parts data
|
||||
this.capabilityQuestionsData.forEach((glass, gpIndex) => {
|
||||
// only look for duplicates forward... to parts that follow after the currently being answered part
|
||||
if (gpIndex > answer.glass) {
|
||||
let suppressUntil;
|
||||
// reset this glass part, in case user is changing their previous answers
|
||||
glass.answerData = null;
|
||||
glass.isSuppressedPart = null;
|
||||
|
||||
// loop through this glass part's part questions, looking for a questionText match
|
||||
glass.capabilityQuestions.forEach((pq, pqIndex) => {
|
||||
// clear out any previously set answers
|
||||
pq.answerSelected = null;
|
||||
|
||||
// clear or set suppressQuestion property for each question
|
||||
if (suppressUntil) {
|
||||
// if suppressUntil has been set, then suppress this question if before it
|
||||
if (pqIndex + 1 < suppressUntil) {
|
||||
pq.suppressQuestion = true;
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
|
||||
// if these match then we have a duplicate question
|
||||
if (pq.questionText.toUpperCase() === answeredQuestionText) {
|
||||
const thisAnsweredCapabilityQuestion =
|
||||
glass.capabilityQuestions[pqIndex];
|
||||
let matchedAnswer;
|
||||
let rejectedAnswers = []; // set as an array, in case we ever have questions with more than 2 answers...
|
||||
|
||||
// which one of this capabilityQuestions' answers matches our answer?
|
||||
pq.answers.forEach((ans) => {
|
||||
if (ans.answerText.toUpperCase() === answeredQuestionAnswer) {
|
||||
matchedAnswer = ans;
|
||||
ans.selected = true;
|
||||
} else {
|
||||
rejectedAnswers.push(ans);
|
||||
ans.selected = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.capabilityQuestionsData[gpIndex].key =
|
||||
this.capabilityQuestionsData[gpIndex].glassLocation +
|
||||
this.capabilityQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
|
||||
// handle suppressing downstream in this question chain
|
||||
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
// ensure that the question that the accepted answer has set to be next is NOT suppressed
|
||||
glass.capabilityQuestions[
|
||||
matchedAnswer.nextQuestionSequence - 1
|
||||
].suppressQuestion = null;
|
||||
// if the duplicate is the 1ST question, then set suppressUntil var to lowest nextQuestion number
|
||||
if (pqIndex === 0) {
|
||||
if (!suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
if (matchedAnswer.nextQuestionSequence < suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle suppressing upstream in this question chain
|
||||
glass.capabilityQuestions.forEach((q) => {
|
||||
q.answers.forEach((thisAns) => {
|
||||
// restore any of the answers that formerly led to the duplicated question
|
||||
if (
|
||||
thisAns.originalNextQuestionSequence ===
|
||||
pq.questionSequence
|
||||
) {
|
||||
// restore original nextQuestionSequence
|
||||
thisAns.nextQuestionSequence =
|
||||
thisAns.originalNextQuestionSequence;
|
||||
this.originalNextQuestionSequence = null;
|
||||
// restore original answerResult
|
||||
if (thisAns.originalAnswerResult) {
|
||||
thisAns.answerResult = thisAns.originalAnswerResult;
|
||||
thisAns.originalAnswerResult = null;
|
||||
}
|
||||
}
|
||||
// search for any answers that lead to the duplicated question
|
||||
if (thisAns.nextQuestionSequence === pq.questionSequence) {
|
||||
// update either the nextQuestionSequence or the answerResult
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence =
|
||||
matchedAnswer.nextQuestionSequence;
|
||||
} else {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence = null;
|
||||
thisAns.originalAnswerResult =
|
||||
thisAns.originalAnswerResult ||
|
||||
thisAns.answerResult;
|
||||
thisAns.answerResult = matchedAnswer.answerResult;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// suppress current question
|
||||
thisAnsweredCapabilityQuestion.suppressQuestion = true;
|
||||
|
||||
const thisGlassPart = "glass" + gpIndex;
|
||||
if (foundDuplicateQuestions[thisGlassPart]) {
|
||||
if (
|
||||
!foundDuplicateQuestions[thisGlassPart].includes(
|
||||
thisAnsweredCapabilityQuestion.questionSequence
|
||||
)
|
||||
) {
|
||||
foundDuplicateQuestions[thisGlassPart].push(
|
||||
thisAnsweredCapabilityQuestion.questionSequence
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foundDuplicateQuestions[thisGlassPart] = [
|
||||
thisAnsweredCapabilityQuestion.questionSequence,
|
||||
];
|
||||
}
|
||||
|
||||
// are there any questions left that are not suppressed?
|
||||
const remainingQuestions = glass.capabilityQuestions.filter((q) => {
|
||||
return !q.suppressQuestion;
|
||||
});
|
||||
|
||||
if (remainingQuestions.length < 1) {
|
||||
// this is the final answer for this glass part
|
||||
|
||||
// mark this part as completely answered by adding answerData
|
||||
const answeredQuestionObj = {
|
||||
questionText: pq.questionText,
|
||||
selectedAnswerText: matchedAnswer.answerText,
|
||||
questionNum: pq.questionSequence,
|
||||
suppressQuestion: pq.suppressQuestion,
|
||||
};
|
||||
// set the answerData as 'already answered'
|
||||
glass.answerData = {
|
||||
answerResult: matchedAnswer.nextQuestionSequence
|
||||
? matchedAnswer.nextQuestionSequence
|
||||
: matchedAnswer.answerResult,
|
||||
answeredQuestions: [answeredQuestionObj],
|
||||
};
|
||||
|
||||
// suppress this glass because it has an answer
|
||||
glass.isSuppressedPart = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.capabilityQuestionsData[gpIndex].key =
|
||||
this.capabilityQuestionsData[gpIndex].glassLocation +
|
||||
this.capabilityQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// DETERMINE ANSWERED QUESTIONS LIST FOR THIS GLASS PART
|
||||
|
||||
// look through all (this part's) part questions for any duplicates that were suppressed;
|
||||
// add them to the list of answered questions if found
|
||||
|
||||
// EX answeredQuestionIndexes: [1,5,11,13]
|
||||
|
||||
// EX foundDuplicateQuestions = {
|
||||
// "glassPart1": [1],
|
||||
// "glassPart2": [7, 10]
|
||||
// };
|
||||
|
||||
const thisPartsDupes = foundDuplicateQuestions["glass" + answer.glassIndex];
|
||||
const completeAnsweredQuestions = answer.answeredQuestions
|
||||
? [...answer.answeredQuestions]
|
||||
: [];
|
||||
const glassPartAnswered = this.capabilityQuestionsData[answer.glassIndex];
|
||||
|
||||
thisPartsDupes?.forEach((dupe) => {
|
||||
// dupe is a single integer
|
||||
const dupeQuestion = glassPartAnswered.capabilityQuestions[dupe - 1];
|
||||
const dupeQuestionAnswer = dupeQuestion.answers.find((q) => q.selected === true);
|
||||
|
||||
glassPartAnswered.capabilityQuestions.forEach((q) => {
|
||||
let includeThisDupeInAnsweredQuestions = false;
|
||||
|
||||
// did one of the answers of this question point to the duplicated question?
|
||||
q.answers.forEach((a) => {
|
||||
if (
|
||||
dupe === a.originalNextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence) &&
|
||||
a.answerText.toUpperCase() ===
|
||||
dupeQuestionAnswer.answerText.toUpperCase()
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
});
|
||||
|
||||
// is this q.questionSequence listed as the duplicated question's nextQuestionSequence?
|
||||
if (
|
||||
q.questionSequence === dupeQuestionAnswer.nextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence)
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
|
||||
if (includeThisDupeInAnsweredQuestions) {
|
||||
completeAnsweredQuestions.push({
|
||||
questionNum: dupeQuestion.questionSequence,
|
||||
questionText: dupeQuestion.questionText,
|
||||
selectedAnswerText: dupeQuestionAnswer.answerText,
|
||||
suppressQuestion: dupeQuestion.suppressQuestion,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// make sure there are no duplicated dupes in the list...
|
||||
const foundInCompleteAnsweredQuestions = new Set();
|
||||
let filteredCompleteAnsweredQuestions = completeAnsweredQuestions.filter((el) => {
|
||||
const duplicate = foundInCompleteAnsweredQuestions.has(el.questionText);
|
||||
foundInCompleteAnsweredQuestions.add(el.questionText);
|
||||
return !duplicate;
|
||||
});
|
||||
filteredCompleteAnsweredQuestions = filteredCompleteAnsweredQuestions.sort(
|
||||
(a, b) => a.questionNum - b.questionNum
|
||||
);
|
||||
|
||||
// set final answer data for the current answered glass part
|
||||
glassPartAnswered.answerData = {
|
||||
answerResult: answer.answerResult,
|
||||
answeredQuestions: filteredCompleteAnsweredQuestions,
|
||||
};
|
||||
|
||||
// this part has been fully answered, so advance to next part's question chain
|
||||
for (let i = answer.glassIndex + 1; i < this.capabilityQuestionsData.length; i++) {
|
||||
// if this part has not yet been fully answered, then make it the current part
|
||||
if (!this.capabilityQuestionsData[i].answerData?.answerResult) {
|
||||
this.currentQuestionChainIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO I'm sure there's a better/simplier way to do this that I'm missing but my brain's fried
|
||||
// Find the part that has some answer that has an answerResult matching the selected answerResult, then get the corresponding answerResult2
|
||||
getCorrespondingAnswerResult2(answerResult1) {
|
||||
return this.capabilityQuestionsData
|
||||
.find((part) =>
|
||||
part.capabilityQuestions.some((question) =>
|
||||
question.answers.some((answer) => answer.answerResult == answerResult1)
|
||||
)
|
||||
) // found part
|
||||
.capabilityQuestions.find((question) =>
|
||||
question.answers.some((answer) => answer.answerResult == answerResult1)
|
||||
) // found question
|
||||
.answers.find((answer) => answer.answerResult == answerResult1).answerResult2;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
loadingModal,
|
||||
Form,
|
||||
questionsPageLayout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.capability-questions {
|
||||
.question-text {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,27 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles molding-questions">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="AlertFewMoreQuestionsWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFewMoreQuestionsHeader"
|
||||
:manualCopy="AlertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(glass, i) in moldingQuestionsData" :key="glass.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[glass.glassLocation + '-' + glass.glassName]"
|
||||
:questionData="glass.questions"
|
||||
:glassIndex="i"
|
||||
v-if="showThisQuestionChain(glass, i)"
|
||||
validationRules="questions-required" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<questions-page-layout
|
||||
ref="questionsPageLayout"
|
||||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
:validationRules="questions - required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@backButtonAction="backButtonAction"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/common-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout";
|
||||
|
||||
// Supporting Files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -85,8 +55,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
moldingQuestionsData: store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)
|
||||
.partsOrQuestions,
|
||||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
|
|
@ -106,15 +75,28 @@ export default {
|
|||
// are there alreadyAnsweredQuestions?
|
||||
const alreadyAnsweredQuestions = store.getters.damage.moldingQuestionAnswers;
|
||||
|
||||
this.moldingQuestionsData = this.pageData.partsOrQuestions
|
||||
this.questionsData = this.pageData.partsOrQuestions
|
||||
.filter((x) => x.parts[0].childPartQuestions.length)
|
||||
.map((glass, i) => {
|
||||
.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.key] = [];
|
||||
// pass in: glass, i, alreadyAnsweredQuestions
|
||||
return this.setupInitialData(glass, i, alreadyAnsweredQuestions);
|
||||
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: {
|
||||
|
|
@ -126,14 +108,8 @@ export default {
|
|||
moldingQuestionsFromPageData && Object.keys(moldingQuestionsFromPageData).length > 0
|
||||
);
|
||||
},
|
||||
showThisQuestionChain(glass, i) {
|
||||
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
||||
return false;
|
||||
} // return false if no questions or if suppressed
|
||||
return this.currentGlassIndex === i || glass.answerData?.answerResult?.length > 0;
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const questionAnswersArray = this.moldingQuestionsData.map((glass) => {
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
return {
|
||||
glassLocation: glass.glassLocation,
|
||||
glassName: glass.glassName,
|
||||
|
|
@ -144,7 +120,7 @@ export default {
|
|||
});
|
||||
|
||||
// clear out answerData for future page loads; must occur prior to store save
|
||||
this.moldingQuestionsData.forEach((glass) => {
|
||||
this.questionsData.forEach((glass) => {
|
||||
glass.answerData = {};
|
||||
});
|
||||
|
||||
|
|
@ -172,313 +148,10 @@ export default {
|
|||
|
||||
this.navigateForward(partsOrQuestions);
|
||||
},
|
||||
handleAnswerUpdates(answer) {
|
||||
// only runs when all questions in a question-chain have been answered
|
||||
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
|
||||
// (does not get run for each invididual question's answer, only when
|
||||
// all relevant questions for the current part have been answered)
|
||||
|
||||
/*
|
||||
answer example format:
|
||||
{
|
||||
"answerResult": "DD11132",
|
||||
"answeredQuestions": [
|
||||
{
|
||||
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||
"selectedAnswerText": "Yes",
|
||||
"questionNum": 1,
|
||||
}
|
||||
],
|
||||
"glassIndex": 0
|
||||
}
|
||||
*/
|
||||
|
||||
// collect a list of the answered questions' numbers, needed later below
|
||||
const answeredQuestionIndexes = [];
|
||||
const foundDuplicateQuestions = [];
|
||||
|
||||
// if user has answered a question differently than anything that was preloaded,
|
||||
// we need to clear out any preloaded answers
|
||||
this.selectedAnswers = {};
|
||||
|
||||
// loop through every answered question on the currently answered glass part
|
||||
answer.answeredQuestions?.forEach((aq) => {
|
||||
// keep track of this question number
|
||||
answeredQuestionIndexes.push(aq.questionNum);
|
||||
|
||||
const answeredQuestionText = aq.questionText.toUpperCase();
|
||||
const answeredQuestionAnswer = aq.selectedAnswerText.toUpperCase();
|
||||
|
||||
// HANDLE DUPLICATE QUESTIONS
|
||||
|
||||
// loop through all glass parts data
|
||||
this.moldingQuestionsData.forEach((glass, gpIndex) => {
|
||||
// only look for duplicates forward... to parts that follow after the currently being answered part
|
||||
if (gpIndex > answer.glassIndex) {
|
||||
let suppressUntil;
|
||||
// reset this glass part, in case user is changing their previous answers
|
||||
glass.answerData = null;
|
||||
glass.isSuppressedPart = null;
|
||||
|
||||
// loop through this glass part's part questions, looking for a questionText match
|
||||
glass.questions.forEach((pq, pqIndex) => {
|
||||
// clear out any previously set answers
|
||||
pq.answerSelected = null;
|
||||
|
||||
// clear or set suppressQuestion property for each question
|
||||
if (suppressUntil) {
|
||||
// if suppressUntil has been set, then suppress this question if before it
|
||||
if (pqIndex + 1 < suppressUntil) {
|
||||
pq.suppressQuestion = true;
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
|
||||
// if these match then we have a duplicate question
|
||||
if (pq.questionText.toUpperCase() === answeredQuestionText) {
|
||||
const thisAnsweredPartQuestion = glass.questions[pqIndex];
|
||||
let matchedAnswer;
|
||||
let rejectedAnswers = []; // set as an array, in case we ever have questions with more than 2 answers...
|
||||
|
||||
// which one of this questions' answers matches our answer?
|
||||
pq.answers.forEach((ans) => {
|
||||
if (ans.answerText.toUpperCase() === answeredQuestionAnswer) {
|
||||
matchedAnswer = ans;
|
||||
ans.selected = true;
|
||||
} else {
|
||||
rejectedAnswers.push(ans);
|
||||
ans.selected = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.moldingQuestionsData[gpIndex].key =
|
||||
this.moldingQuestionsData[gpIndex].glassLocation +
|
||||
this.moldingQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
|
||||
// handle suppressing downstream in this question chain
|
||||
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
// ensure that the question that the accepted answer has set to be next is NOT suppressed
|
||||
glass.questions[
|
||||
matchedAnswer.nextQuestionSequence - 1
|
||||
].suppressQuestion = null;
|
||||
// if the duplicate is the 1ST question, then set suppressUntil var to lowest nextQuestion number
|
||||
if (pqIndex === 0) {
|
||||
if (!suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
if (matchedAnswer.nextQuestionSequence < suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle suppressing upstream in this question chain
|
||||
glass.questions.forEach((q) => {
|
||||
q.answers.forEach((thisAns) => {
|
||||
// restore any of the answers that formerly led to the duplicated question
|
||||
if (
|
||||
thisAns.originalNextQuestionSequence ===
|
||||
pq.questionSequence
|
||||
) {
|
||||
// restore original nextQuestionSequence
|
||||
thisAns.nextQuestionSequence =
|
||||
thisAns.originalNextQuestionSequence;
|
||||
this.originalNextQuestionSequence = null;
|
||||
// restore original answerResult
|
||||
if (thisAns.originalAnswerResult) {
|
||||
thisAns.answerResult = thisAns.originalAnswerResult;
|
||||
thisAns.originalAnswerResult = null;
|
||||
}
|
||||
}
|
||||
// search for any answers that lead to the duplicated question
|
||||
if (thisAns.nextQuestionSequence === pq.questionSequence) {
|
||||
// update either the nextQuestionSequence or the answerResult
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence =
|
||||
matchedAnswer.nextQuestionSequence;
|
||||
} else {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence = null;
|
||||
thisAns.originalAnswerResult =
|
||||
thisAns.originalAnswerResult ||
|
||||
thisAns.answerResult;
|
||||
thisAns.answerResult = matchedAnswer.answerResult;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// suppress current question
|
||||
thisAnsweredPartQuestion.suppressQuestion = true;
|
||||
|
||||
const thisGlassPart = "glass" + gpIndex;
|
||||
if (foundDuplicateQuestions[thisGlassPart]) {
|
||||
if (
|
||||
!foundDuplicateQuestions[thisGlassPart].includes(
|
||||
thisAnsweredPartQuestion.questionSequence
|
||||
)
|
||||
) {
|
||||
foundDuplicateQuestions[thisGlassPart].push(
|
||||
thisAnsweredPartQuestion.questionSequence
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foundDuplicateQuestions[thisGlassPart] = [
|
||||
thisAnsweredPartQuestion.questionSequence,
|
||||
];
|
||||
}
|
||||
|
||||
// are there any questions left that are not suppressed?
|
||||
const remainingQuestions = glass.questions.filter((q) => {
|
||||
return !q.suppressQuestion;
|
||||
});
|
||||
|
||||
if (remainingQuestions.length < 1) {
|
||||
// this is the final answer for this glass part
|
||||
|
||||
// mark this part as completely answered by adding answerData
|
||||
const answeredQuestionObj = {
|
||||
questionText: pq.questionText,
|
||||
selectedAnswerText: matchedAnswer.answerText,
|
||||
questionNum: pq.questionSequence,
|
||||
suppressQuestion: pq.suppressQuestion,
|
||||
};
|
||||
// set the answerData as 'already answered'
|
||||
glass.answerData = {
|
||||
answerResult: matchedAnswer.nextQuestionSequence
|
||||
? matchedAnswer.nextQuestionSequence
|
||||
: matchedAnswer.answerResult,
|
||||
answeredQuestions: [answeredQuestionObj],
|
||||
};
|
||||
|
||||
// suppress this glass because it has an answer
|
||||
glass.isSuppressedPart = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.moldingQuestionsData[gpIndex].key =
|
||||
this.moldingQuestionsData[gpIndex].glassLocation +
|
||||
this.moldingQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// DETERMINE ANSWERED QUESTIONS LIST FOR THIS GLASS PART
|
||||
|
||||
// look through all (this part's) part questions for any duplicates that were suppressed;
|
||||
// add them to the list of answered questions if found
|
||||
|
||||
// EX answeredQuestionIndexes: [1,5,11,13]
|
||||
|
||||
// EX foundDuplicateQuestions = {
|
||||
// "glassPart1": [1],
|
||||
// "glassPart2": [7, 10]
|
||||
// };
|
||||
|
||||
const thisPartsDupes = foundDuplicateQuestions["glass" + answer.glassIndex];
|
||||
const completeAnsweredQuestions = answer.answeredQuestions
|
||||
? [...answer.answeredQuestions]
|
||||
: [];
|
||||
const glassPartAnswered = this.moldingQuestionsData[answer.glassIndex];
|
||||
|
||||
thisPartsDupes?.forEach((dupe) => {
|
||||
// dupe is a single integer
|
||||
const dupeQuestion = glassPartAnswered.questions[dupe - 1];
|
||||
const dupeQuestionAnswer = dupeQuestion.answers.find((q) => q.selected === true);
|
||||
|
||||
glassPartAnswered.questions.forEach((q) => {
|
||||
let includeThisDupeInAnsweredQuestions = false;
|
||||
|
||||
// did one of the answers of this question point to the duplicated question?
|
||||
q.answers.forEach((a) => {
|
||||
if (
|
||||
dupe === a.originalNextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence) &&
|
||||
a.answerText.toUpperCase() ===
|
||||
dupeQuestionAnswer.answerText.toUpperCase()
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
});
|
||||
|
||||
// is this q.questionSequence listed as the duplicated question's nextQuestionSequence?
|
||||
if (
|
||||
q.questionSequence === dupeQuestionAnswer.nextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence)
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
|
||||
if (includeThisDupeInAnsweredQuestions) {
|
||||
completeAnsweredQuestions.push({
|
||||
questionNum: dupeQuestion.questionSequence,
|
||||
questionText: dupeQuestion.questionText,
|
||||
selectedAnswerText: dupeQuestionAnswer.answerText,
|
||||
suppressQuestion: dupeQuestion.suppressQuestion,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// make sure there are no duplicated dupes in the list...
|
||||
const foundInCompleteAnsweredQuestions = new Set();
|
||||
let filteredCompleteAnsweredQuestions = completeAnsweredQuestions.filter((el) => {
|
||||
const duplicate = foundInCompleteAnsweredQuestions.has(el.questionText);
|
||||
foundInCompleteAnsweredQuestions.add(el.questionText);
|
||||
return !duplicate;
|
||||
});
|
||||
filteredCompleteAnsweredQuestions = filteredCompleteAnsweredQuestions.sort(
|
||||
(a, b) => a.questionNum - b.questionNum
|
||||
);
|
||||
|
||||
// set final answer data for the current answered glass part
|
||||
glassPartAnswered.answerData = {
|
||||
answerResult: answer.answerResult,
|
||||
answeredQuestions: filteredCompleteAnsweredQuestions,
|
||||
};
|
||||
|
||||
// this part has been fully answered, so advance to next molding question chain
|
||||
for (let i = answer.glassIndex + 1; i < this.moldingQuestionsData.length; i++) {
|
||||
// if this part has not yet been fully answered, then make it the current part
|
||||
if (!this.moldingQuestionsData[i].answerData?.answerResult) {
|
||||
this.currentGlassIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
loadingModal,
|
||||
Form,
|
||||
questionsPageLayout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.molding-questions {
|
||||
.question-text {
|
||||
margin-bottom: 0.5rem;
|
||||
span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,27 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles part-questions">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="AlertFewMoreQuestionsWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFewMoreQuestionsHeader"
|
||||
:manualCopy="AlertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(glass, i) in partsQuestionsData" :key="glass.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[glass.glassLocation + '-' + glass.glassName]"
|
||||
:questionData="glass.questions"
|
||||
:glassIndex="i"
|
||||
v-if="showThisQuestionChain(glass, i)"
|
||||
validationRules="questions-required" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<questions-page-layout
|
||||
ref="questionsPageLayout"
|
||||
:isMetaValid="meta.valid"
|
||||
:alertFewMoreQuestionsHeader="AlertFewMoreQuestionsHeader"
|
||||
:alertFewMoreQuestionsCopy="AlertFewMoreQuestionsCopy"
|
||||
:questionsData="questionsData"
|
||||
:validationRules="questions - required"
|
||||
v-model="selectedAnswers"
|
||||
@forwardButtonAction="forwardButtonAction"
|
||||
@backButtonAction="backButtonAction"
|
||||
:index="currentGlassIndex" />
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/common-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||
import questionsPageLayout from "@/common-components/questions-page-layout/questions-page-layout";
|
||||
|
||||
// Supporting Files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -63,6 +33,7 @@ defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
|||
|
||||
export default {
|
||||
name: "part-questions",
|
||||
mixins: [vehicleQuestionsMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
|
@ -84,7 +55,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
partsQuestionsData: [],
|
||||
questionsData: [],
|
||||
selectedAnswers: {},
|
||||
currentGlassIndex: 0,
|
||||
};
|
||||
|
|
@ -100,20 +71,33 @@ export default {
|
|||
return this.$store.getters.pageData(fmgPageValues.PART_QUESTIONS);
|
||||
},
|
||||
},
|
||||
mixins: [vehicleQuestionsMixin],
|
||||
mounted() {
|
||||
// are there alreadyAnsweredQuestions?
|
||||
const alreadyAnsweredQuestions = store.getters.damage.partQuestionAnswers;
|
||||
|
||||
this.partsQuestionsData = this.pageData.partsOrQuestions
|
||||
this.questionsData = this.pageData.partsOrQuestions
|
||||
.filter((x) => x.partQuestions)
|
||||
.map((glass, i) => {
|
||||
.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.key] = [];
|
||||
// pass in: glass, i, alreadyAnsweredQuestions
|
||||
return this.setupInitialData(glass, i, alreadyAnsweredQuestions);
|
||||
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: {
|
||||
|
|
@ -121,14 +105,8 @@ export default {
|
|||
const partQuestionsFromPageData = store.getters.pageData(fmgPageValues.PART_QUESTIONS);
|
||||
return partQuestionsFromPageData && Object.keys(partQuestionsFromPageData).length > 0;
|
||||
},
|
||||
showThisQuestionChain(glass, i) {
|
||||
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
||||
return false;
|
||||
} // return false if no questions or if suppressed
|
||||
return this.currentGlassIndex === i || glass.answerData?.answerResult?.length > 0;
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const questionAnswersArray = this.partsQuestionsData.map((glass) => {
|
||||
const questionAnswersArray = this.questionsData.map((glass) => {
|
||||
return {
|
||||
glassLocation: glass.glassLocation,
|
||||
glassName: glass.glassName,
|
||||
|
|
@ -139,7 +117,7 @@ export default {
|
|||
});
|
||||
|
||||
// clear out answerData for future page loads; must occur prior to store save
|
||||
this.partsQuestionsData.forEach((glass) => {
|
||||
this.questionsData.forEach((glass) => {
|
||||
glass.answerData = {};
|
||||
});
|
||||
|
||||
|
|
@ -151,321 +129,22 @@ export default {
|
|||
);
|
||||
|
||||
// call API parts method
|
||||
const partsLookup = await this.dispatchStoreAction(storeActions.GET_PARTS).catch(() => {
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
});
|
||||
|
||||
const glassPiecePartsForStore = partsLookup.data.glassPieceParts;
|
||||
|
||||
this.navigateForward(glassPiecePartsForStore);
|
||||
},
|
||||
handleAnswerUpdates(answer) {
|
||||
// only runs when all questions in a question-chain have been answered
|
||||
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
|
||||
// (does not get run for each invididual question's answer, only when
|
||||
// all relevant questions for the current part have been answered)
|
||||
|
||||
/*
|
||||
answer example format:
|
||||
{
|
||||
"answerResult": "DD11132",
|
||||
"answeredQuestions": [
|
||||
{
|
||||
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||
"selectedAnswerText": "Yes",
|
||||
"questionNum": 1,
|
||||
}
|
||||
],
|
||||
"glassIndex": 0
|
||||
const partsLookup = await this.dispatchStoreAction(this.storeActions.GET_PARTS).catch(
|
||||
() => {
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
}
|
||||
*/
|
||||
|
||||
// collect a list of the answered questions' numbers, needed later below
|
||||
const answeredQuestionIndexes = [];
|
||||
const foundDuplicateQuestions = [];
|
||||
|
||||
// if user has answered a question differently than anything that was preloaded,
|
||||
// we need to clear out any preloaded answers
|
||||
this.selectedAnswers = {};
|
||||
|
||||
// loop through every answered question on the currently answered glass part
|
||||
answer.answeredQuestions?.forEach((aq) => {
|
||||
// keep track of this question number
|
||||
answeredQuestionIndexes.push(aq.questionNum);
|
||||
|
||||
const answeredQuestionText = aq.questionText.toUpperCase();
|
||||
const answeredQuestionAnswer = aq.selectedAnswerText.toUpperCase();
|
||||
|
||||
// HANDLE DUPLICATE QUESTIONS
|
||||
|
||||
// loop through all glass parts data
|
||||
this.partsQuestionsData.forEach((glass, gpIndex) => {
|
||||
// only look for duplicates forward... to parts that follow after the currently being answered part
|
||||
if (gpIndex > answer.glassIndex) {
|
||||
let suppressUntil;
|
||||
// reset this glass part, in case user is changing their previous answers
|
||||
glass.answerData = null;
|
||||
glass.isSuppressedPart = null;
|
||||
|
||||
// loop through this glass part's questions, looking for a questionText match
|
||||
glass.questions.forEach((pq, pqIndex) => {
|
||||
// clear out any previously set answers
|
||||
pq.answerSelected = null;
|
||||
|
||||
// clear or set suppressQuestion property for each question
|
||||
if (suppressUntil) {
|
||||
// if suppressUntil has been set, then suppress this question if before it
|
||||
if (pqIndex + 1 < suppressUntil) {
|
||||
pq.suppressQuestion = true;
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
} else {
|
||||
pq.suppressQuestion = null;
|
||||
}
|
||||
|
||||
// if these match then we have a duplicate question
|
||||
if (pq.questionText.toUpperCase() === answeredQuestionText) {
|
||||
const thisAnsweredQuestion = glass.questions[pqIndex];
|
||||
let matchedAnswer;
|
||||
let rejectedAnswers = []; // set as an array, in case we ever have questions with more than 2 answers...
|
||||
|
||||
// which one of this questions' answers matches our answer?
|
||||
pq.answers.forEach((ans) => {
|
||||
if (ans.answerText.toUpperCase() === answeredQuestionAnswer) {
|
||||
matchedAnswer = ans;
|
||||
ans.selected = true;
|
||||
} else {
|
||||
rejectedAnswers.push(ans);
|
||||
ans.selected = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.partsQuestionsData[gpIndex].key =
|
||||
this.partsQuestionsData[gpIndex].glassLocation +
|
||||
this.partsQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
|
||||
// handle suppressing downstream in this question chain
|
||||
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
// ensure that the question that the accepted answer has set to be next is NOT suppressed
|
||||
glass.questions[
|
||||
matchedAnswer.nextQuestionSequence - 1
|
||||
].suppressQuestion = null;
|
||||
// if the duplicate is the 1ST question, then set suppressUntil var to lowest nextQuestion number
|
||||
if (pqIndex === 0) {
|
||||
if (!suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
if (matchedAnswer.nextQuestionSequence < suppressUntil) {
|
||||
suppressUntil = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle suppressing upstream in this question chain
|
||||
glass.questions.forEach((q) => {
|
||||
q.answers.forEach((thisAns) => {
|
||||
// restore any of the answers that formerly led to the duplicated question
|
||||
if (
|
||||
thisAns.originalNextQuestionSequence ===
|
||||
pq.questionSequence
|
||||
) {
|
||||
// restore original nextQuestionSequence
|
||||
thisAns.nextQuestionSequence =
|
||||
thisAns.originalNextQuestionSequence;
|
||||
this.originalNextQuestionSequence = null;
|
||||
// restore original answerResult
|
||||
if (thisAns.originalAnswerResult) {
|
||||
thisAns.answerResult = thisAns.originalAnswerResult;
|
||||
thisAns.originalAnswerResult = null;
|
||||
}
|
||||
}
|
||||
// search for any answers that lead to the duplicated question
|
||||
if (thisAns.nextQuestionSequence === pq.questionSequence) {
|
||||
// update either the nextQuestionSequence or the answerResult
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence =
|
||||
matchedAnswer.nextQuestionSequence;
|
||||
} else {
|
||||
thisAns.originalNextQuestionSequence =
|
||||
thisAns.nextQuestionSequence;
|
||||
thisAns.nextQuestionSequence = null;
|
||||
thisAns.originalAnswerResult =
|
||||
thisAns.originalAnswerResult ||
|
||||
thisAns.answerResult;
|
||||
thisAns.answerResult = matchedAnswer.answerResult;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// suppress current question
|
||||
thisAnsweredQuestion.suppressQuestion = true;
|
||||
|
||||
const thisGlassPart = "glass" + gpIndex;
|
||||
if (foundDuplicateQuestions[thisGlassPart]) {
|
||||
if (
|
||||
!foundDuplicateQuestions[thisGlassPart].includes(
|
||||
thisAnsweredQuestion.questionSequence
|
||||
)
|
||||
) {
|
||||
foundDuplicateQuestions[thisGlassPart].push(
|
||||
thisAnsweredQuestion.questionSequence
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foundDuplicateQuestions[thisGlassPart] = [
|
||||
thisAnsweredQuestion.questionSequence,
|
||||
];
|
||||
}
|
||||
|
||||
// are there any questions left that are not suppressed?
|
||||
const remainingQuestions = glass.questions.filter((q) => {
|
||||
return !q.suppressQuestion;
|
||||
});
|
||||
|
||||
if (remainingQuestions.length < 1) {
|
||||
// this is the final answer for this glass part
|
||||
|
||||
// mark this part as completely answered by adding answerData
|
||||
const answeredQuestionObj = {
|
||||
questionText: pq.questionText,
|
||||
selectedAnswerText: matchedAnswer.answerText,
|
||||
questionNum: pq.questionSequence,
|
||||
suppressQuestion: pq.suppressQuestion,
|
||||
};
|
||||
// set the answerData as 'already answered'
|
||||
glass.answerData = {
|
||||
answerResult: matchedAnswer.nextQuestionSequence
|
||||
? matchedAnswer.nextQuestionSequence
|
||||
: matchedAnswer.answerResult,
|
||||
answeredQuestions: [answeredQuestionObj],
|
||||
};
|
||||
|
||||
// suppress this glass because it has an answer
|
||||
glass.isSuppressedPart = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update the key to re-render this part's question-chain component
|
||||
this.partsQuestionsData[gpIndex].key =
|
||||
this.partsQuestionsData[gpIndex].glassLocation +
|
||||
this.partsQuestionsData[gpIndex].glassName +
|
||||
Date.now().toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// DETERMINE ANSWERED QUESTIONS LIST FOR THIS GLASS PART
|
||||
|
||||
// look through all (this part's) part questions for any duplicates that were suppressed;
|
||||
// add them to the list of answered questions if found
|
||||
|
||||
// EX answeredQuestionIndexes: [1,5,11,13]
|
||||
|
||||
// EX foundDuplicateQuestions = {
|
||||
// "glassPart1": [1],
|
||||
// "glassPart2": [7, 10]
|
||||
// };
|
||||
|
||||
const thisPartsDupes = foundDuplicateQuestions["glass" + answer.glassIndex];
|
||||
const completeAnsweredQuestions = answer.answeredQuestions
|
||||
? [...answer.answeredQuestions]
|
||||
: [];
|
||||
const glassPartAnswered = this.partsQuestionsData[answer.glassIndex];
|
||||
|
||||
thisPartsDupes?.forEach((dupe) => {
|
||||
// dupe is a single integer
|
||||
const dupeQuestion = glassPartAnswered.questions[dupe - 1];
|
||||
const dupeQuestionAnswer = dupeQuestion.answers.find((q) => q.selected === true);
|
||||
|
||||
glassPartAnswered.questions.forEach((q) => {
|
||||
let includeThisDupeInAnsweredQuestions = false;
|
||||
|
||||
// did one of the answers of this question point to the duplicated question?
|
||||
q.answers.forEach((a) => {
|
||||
if (
|
||||
dupe === a.originalNextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence) &&
|
||||
a.answerText.toUpperCase() ===
|
||||
dupeQuestionAnswer.answerText.toUpperCase()
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
});
|
||||
|
||||
// is this q.questionSequence listed as the duplicated question's nextQuestionSequence?
|
||||
if (
|
||||
q.questionSequence === dupeQuestionAnswer.nextQuestionSequence &&
|
||||
answeredQuestionIndexes.includes(q.questionSequence)
|
||||
) {
|
||||
includeThisDupeInAnsweredQuestions = true;
|
||||
}
|
||||
|
||||
if (includeThisDupeInAnsweredQuestions) {
|
||||
completeAnsweredQuestions.push({
|
||||
questionNum: dupeQuestion.questionSequence,
|
||||
questionText: dupeQuestion.questionText,
|
||||
selectedAnswerText: dupeQuestionAnswer.answerText,
|
||||
suppressQuestion: dupeQuestion.suppressQuestion,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// make sure there are no duplicated dupes in the list...
|
||||
const foundInCompleteAnsweredQuestions = new Set();
|
||||
let filteredCompleteAnsweredQuestions = completeAnsweredQuestions.filter((el) => {
|
||||
const duplicate = foundInCompleteAnsweredQuestions.has(el.questionText);
|
||||
foundInCompleteAnsweredQuestions.add(el.questionText);
|
||||
return !duplicate;
|
||||
});
|
||||
filteredCompleteAnsweredQuestions = filteredCompleteAnsweredQuestions.sort(
|
||||
(a, b) => a.questionNum - b.questionNum
|
||||
);
|
||||
|
||||
// set final answer data for the current answered glass part
|
||||
glassPartAnswered.answerData = {
|
||||
answerResult: answer.answerResult,
|
||||
answeredQuestions: filteredCompleteAnsweredQuestions,
|
||||
};
|
||||
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
||||
|
||||
// this part has been fully answered, so advance to next part's question chain
|
||||
for (let i = answer.glassIndex + 1; i < this.partsQuestionsData.length; i++) {
|
||||
// if this part has not yet been fully answered, then make it the current part
|
||||
if (!this.partsQuestionsData[i].answerData?.answerResult) {
|
||||
this.currentGlassIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugger; // eslint-disable-line no-debugger
|
||||
|
||||
this.navigateForward(glassPartsForStore);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
vehicleBanner,
|
||||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
loadingModal,
|
||||
Form,
|
||||
questionsPageLayout,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.part-questions {
|
||||
.question-text {
|
||||
margin-bottom: 0.5rem;
|
||||
span {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@
|
|||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<!-- includeSearchIcon and questionAlignment, placeholderText for testing only. Remove after testing. -->
|
||||
<textboxQuestion
|
||||
includeSearchIcon
|
||||
questionAlignment="center"
|
||||
placeholderText="Styled for testing only"
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
inputId="vin"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { storeMutations } from "@/constants/store-mutations.js";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected";
|
||||
import { navigationScenarios } from "../router/router-constants/navigation-scenarios";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
|
|
@ -67,9 +66,9 @@ export default {
|
|||
currentPageComesAfterPage(currentPage = this.$route.query.fmgPage, fmgPage) {
|
||||
return this.comparePageIndices(currentPage, fmgPage) > 0;
|
||||
},
|
||||
setupInitialData(glass, i, alreadyAnsweredQuestions, vm) {
|
||||
glass.key = glass.glassLocation + "-" + glass.glassName;
|
||||
setupInitialData(glass, index, alreadyAnsweredQuestions, vm) {
|
||||
const self = vm ?? this;
|
||||
|
||||
// clear answerData if no questions are already answered
|
||||
if (!alreadyAnsweredQuestions) {
|
||||
glass.answerData = null;
|
||||
|
|
@ -94,33 +93,39 @@ export default {
|
|||
let answerString = "";
|
||||
|
||||
// loop through answeredQuestions for matches
|
||||
answeredGlass.answeredQuestions.forEach((aq) => {
|
||||
if (!aq.questionNum || !aq.selectedAnswerText) {
|
||||
answeredGlass.answeredQuestions.forEach((answeredQuestion) => {
|
||||
if (!answeredQuestion.questionNum || !answeredQuestion.selectedAnswerText) {
|
||||
return;
|
||||
}
|
||||
// determine which answer was previously chosen
|
||||
const chosenAns = glass.questions[aq.questionNum - 1].answers.find((a) => {
|
||||
const chosenAns = glass.questions[
|
||||
answeredQuestion.questionNum - 1
|
||||
].answers.find((a) => {
|
||||
return (
|
||||
a.answerText.toUpperCase() === aq.selectedAnswerText.toUpperCase()
|
||||
a.answerText.toUpperCase() ===
|
||||
answeredQuestion.selectedAnswerText.toUpperCase()
|
||||
);
|
||||
});
|
||||
// set the answerString to use for answerSelected
|
||||
if (chosenAns.nextQuestionSequence) {
|
||||
answerString = `${aq.questionNum}|nextQuestion|${chosenAns.nextQuestionSequence}|${chosenAns.answerText}`;
|
||||
answerString = `${answeredQuestion.questionNum}|nextQuestion|${chosenAns.nextQuestionSequence}|${chosenAns.answerText}`;
|
||||
} else {
|
||||
answerString = `${aq.questionNum}|answer|${chosenAns.answerResult}|${chosenAns.answerText}`;
|
||||
answerString = `${answeredQuestion.questionNum}|answer|${chosenAns.answerResult}|${chosenAns.answerText}`;
|
||||
}
|
||||
|
||||
// mark this question as answered (question-chain will read this)
|
||||
glass.questions[aq.questionNum - 1].answerSelected = answerString;
|
||||
glass.questions[answeredQuestion.questionNum - 1].answerSelected =
|
||||
answerString;
|
||||
// mark this question as suppressed if needed (question-chain uses this)
|
||||
if (aq.suppressQuestion) {
|
||||
glass.questions[aq.questionNum - 1].suppressQuestion = true;
|
||||
if (answeredQuestion.suppressThisQuestion) {
|
||||
glass.questions[
|
||||
answeredQuestion.questionNum - 1
|
||||
].suppressThisQuestion = true;
|
||||
}
|
||||
});
|
||||
|
||||
// advance the currentGlassIndex
|
||||
self.currentGlassIndex = i;
|
||||
self.currentGlassIndex = index;
|
||||
|
||||
const answerResult = answeredGlass.partNum
|
||||
? answeredGlass.partNum
|
||||
|
|
@ -135,20 +140,221 @@ export default {
|
|||
}
|
||||
});
|
||||
|
||||
// Set up watch for each set of glass questions
|
||||
// (updated when all questions for a glass have been answered in question-chain)
|
||||
self.$watch(
|
||||
"selectedAnswers." + glass.glassLocation + "-" + glass.glassName,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
self.handleAnswerUpdates(newValue);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
return glass;
|
||||
},
|
||||
handleAnswerUpdates(answer, glassKey, vm) {
|
||||
// only runs when all questions in a question-chain have been answered
|
||||
// when selectedAnswers updates, user has completed this part's question chain and has a final answer
|
||||
// (does not get run for each invididual question's answer, only when
|
||||
// all relevant questions for the current part have been answered)
|
||||
|
||||
/* answer example format:
|
||||
{
|
||||
"answerResult": "DD11132",
|
||||
"answeredQuestions": [
|
||||
{
|
||||
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||
"selectedAnswerText": "Yes",
|
||||
"questionNum": 1,
|
||||
}
|
||||
],
|
||||
"index": 0
|
||||
}
|
||||
*/
|
||||
|
||||
const self = vm ?? this;
|
||||
|
||||
// clear out any preloaded answers
|
||||
self.selectedAnswers = {};
|
||||
|
||||
// loop through every answered question on the currently answered glass part
|
||||
answer.answeredQuestions?.forEach((answeredQuestion) => {
|
||||
/* answeredQuestion example format:
|
||||
{
|
||||
"questionText": "Is your Grand Cherokee the Laredo model?",
|
||||
"selectedAnswerText": "Yes",
|
||||
"questionNum": 1,
|
||||
}
|
||||
*/
|
||||
|
||||
const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
|
||||
const answeredQuestionAnswer = answeredQuestion.selectedAnswerText.toUpperCase();
|
||||
|
||||
// HANDLE DUPLICATE QUESTIONS
|
||||
|
||||
// loop through all glass data
|
||||
self.questionsData.forEach((glass, glassIndex) => {
|
||||
/* glass example format:
|
||||
{
|
||||
"glassName": "Single",
|
||||
"glassLocation": "Windshield",
|
||||
"parts": null,
|
||||
"questions": [
|
||||
{
|
||||
"questionSequence": 1,
|
||||
"questionText": "Is your vehicle equipped with a black dotted pattern behind the rear view mirror, known as a third visor frit?",
|
||||
"answers": [
|
||||
{
|
||||
"answerResult": "DW01537",
|
||||
"answerText": "Yes",
|
||||
"nextQuestionSequence": null
|
||||
},
|
||||
{
|
||||
"answerResult": "DW01537b",
|
||||
"answerText": "No",
|
||||
"nextQuestionSequence": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"answerKey": "Windshield-Single",
|
||||
"answerData": null
|
||||
}
|
||||
*/
|
||||
|
||||
// limit duplicate search to glass pieces that follow after the currently being answered glass piece
|
||||
if (glassIndex > answer.index) {
|
||||
let indexToSuppressTo;
|
||||
// reset this glass piece, in case user is changing their previous answers
|
||||
glass.answerData = null;
|
||||
glass.isSuppressedPart = null;
|
||||
|
||||
// loop through this glass piece's questions, looking for a questionText match
|
||||
glass.questions.forEach((question, questionIndex) => {
|
||||
// clear out any previously set answers
|
||||
question.answerSelected = null;
|
||||
|
||||
// clear or set suppressThisQuestion property for each question
|
||||
if (indexToSuppressTo && questionIndex + 1 < indexToSuppressTo) {
|
||||
question.suppressThisQuestion = true;
|
||||
} else {
|
||||
question.suppressThisQuestion = null;
|
||||
}
|
||||
|
||||
// handle duplicate questions
|
||||
if (question.questionText.toUpperCase() === answeredQuestionText) {
|
||||
let matchedAnswer;
|
||||
|
||||
// handle matching answer in duplicated question
|
||||
question.answers.forEach((ans) => {
|
||||
if (ans.answerText.toUpperCase() === answeredQuestionAnswer) {
|
||||
matchedAnswer = ans;
|
||||
ans.selected = true;
|
||||
} else {
|
||||
ans.selected = null;
|
||||
}
|
||||
});
|
||||
|
||||
// handle duplicate's nextQuestion logic on other related questions
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
// clear any suppression on the nextQuestion
|
||||
glass.questions[
|
||||
matchedAnswer.nextQuestionSequence - 1
|
||||
].suppressThisQuestion = null;
|
||||
// if the duplicate is 1ST question in array, set indexToSuppressTo
|
||||
if (questionIndex === 0) {
|
||||
if (
|
||||
!indexToSuppressTo ||
|
||||
matchedAnswer.nextQuestionSequence < indexToSuppressTo
|
||||
) {
|
||||
indexToSuppressTo = matchedAnswer.nextQuestionSequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update answers in this glass piece's questions with duplication logic modifications
|
||||
glass.questions.forEach((q) => {
|
||||
q.answers.forEach((a) => {
|
||||
// revert any previously set nextQuestion logic modifications
|
||||
if (
|
||||
a.originalNextQuestionSequence ===
|
||||
question.questionSequence
|
||||
) {
|
||||
// restore original nextQuestionSequence
|
||||
a.nextQuestionSequence = a.originalNextQuestionSequence;
|
||||
self.originalNextQuestionSequence = null;
|
||||
// restore original answerResult
|
||||
if (a.originalAnswerResult) {
|
||||
a.answerResult = a.originalAnswerResult;
|
||||
a.originalAnswerResult = null;
|
||||
}
|
||||
}
|
||||
// modify logic on any related questions
|
||||
if (a.nextQuestionSequence === question.questionSequence) {
|
||||
// update either nextQuestionSequence or answerResult
|
||||
|
||||
// update questions that lead to duplicated question
|
||||
if (matchedAnswer.nextQuestionSequence) {
|
||||
a.originalNextQuestionSequence =
|
||||
a.nextQuestionSequence;
|
||||
a.nextQuestionSequence =
|
||||
matchedAnswer.nextQuestionSequence;
|
||||
} else {
|
||||
a.originalNextQuestionSequence =
|
||||
a.nextQuestionSequence;
|
||||
a.nextQuestionSequence = null;
|
||||
a.originalAnswerResult =
|
||||
a.originalAnswerResult || a.answerResult;
|
||||
a.answerResult = matchedAnswer.answerResult;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// suppress current question
|
||||
question.suppressThisQuestion = true;
|
||||
|
||||
// are there any questions left that are not suppressed?
|
||||
const remainingQuestions = glass.questions.filter((q) => {
|
||||
return !q.suppressThisQuestion;
|
||||
});
|
||||
|
||||
if (remainingQuestions.length < 1) {
|
||||
// this is the final answer for this glass piece
|
||||
|
||||
// mark this glass piece as completely answered by adding answerData
|
||||
const answeredQuestionObj = {
|
||||
questionText: question.questionText,
|
||||
selectedAnswerText: matchedAnswer.answerText,
|
||||
questionNum: question.questionSequence,
|
||||
suppressThisQuestion: question.suppressThisQuestion,
|
||||
};
|
||||
// set the answerData (used as indicator that it has been already answered)
|
||||
glass.answerData = {
|
||||
answerResult: matchedAnswer.nextQuestionSequence
|
||||
? matchedAnswer.nextQuestionSequence
|
||||
: matchedAnswer.answerResult,
|
||||
answeredQuestions: [answeredQuestionObj],
|
||||
};
|
||||
|
||||
// suppress this glass piece because it has an answer
|
||||
glass.isSuppressedPart = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update key to force re-render of glass piece with duplicate question in case user changes previous related answer in the chain
|
||||
self.questionsData[glassIndex].key =
|
||||
self.questionsData[glassIndex].key + Date.now().toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// set final answer data for the current answered glass part
|
||||
self.questionsData[answer.index].answerData = {
|
||||
answerResult: answer.answerResult,
|
||||
answeredQuestions: answer.answeredQuestions,
|
||||
};
|
||||
|
||||
// this part has been fully answered, so advance to next part's question chain
|
||||
for (let i = answer.index + 1; i < this.questionsData.length; i++) {
|
||||
// if this part has not yet been fully answered, then make it the current part
|
||||
if (!this.questionsData[i].answerData?.answerResult) {
|
||||
this.currentGlassIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
// Can't use `this` because navigateForward is also called from vin-pages-mixin
|
||||
async navigateForward(partsOrQuestions, vm) {
|
||||
const self = vm ?? this;
|
||||
|
|
@ -240,10 +446,16 @@ export default {
|
|||
} else {
|
||||
// if single parts only
|
||||
const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions);
|
||||
|
||||
// save to store lineItems.glassParts
|
||||
self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts);
|
||||
|
||||
self.$refs.loadingModal.showModal();
|
||||
if (self.$refs.questionsPage) {
|
||||
self.$refs.questionsPage.showLoadingModal();
|
||||
} else if (self.$refs.loadingModal) {
|
||||
self.$refs.loadingModal.showModal();
|
||||
}
|
||||
|
||||
navigateToHeritageFunnel();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -337,24 +337,6 @@ describe("vehicle-questions-mixin", () => {
|
|||
});
|
||||
|
||||
describe("setupInitialData", () => {
|
||||
describe("should always", () => {
|
||||
test("return glass with new key attribute", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const glass = {
|
||||
glassName: "Single",
|
||||
glassLocation: "Windshield",
|
||||
};
|
||||
const i = 0;
|
||||
|
||||
// Act
|
||||
const returnedGlass = await wrapper.vm.setupInitialData(glass, i);
|
||||
|
||||
// Assert
|
||||
expect(returnedGlass).toMatchObject({ key: "Windshield-Single" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("if no alreadyAnsweredQuestions", () => {
|
||||
test("should return glass with answerData of null", async () => {
|
||||
// Arrange
|
||||
|
|
@ -430,6 +412,816 @@ describe("vehicle-questions-mixin", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("handleAnswerUpdates", () => {
|
||||
describe("selectedAnswers", () => {
|
||||
test("should be cleared to be empty", () => {
|
||||
// Arrange
|
||||
const answer = {
|
||||
answerResult: "DD11132",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "Yes",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.selectedAnswers = { test: "mockSelectedAnswers" };
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [],
|
||||
answerData: "testAnswerData1",
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.selectedAnswers).toMatchObject({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("questions in glass parts that are after the answered glass", () => {
|
||||
test("should have answerData cleared", () => {
|
||||
// Arrange
|
||||
const answer = {
|
||||
answerResult: "DD11132",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "Yes",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [],
|
||||
answerData: "testAnswerData1",
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [],
|
||||
answerData: "testAnswerData2",
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.questionsData[1].answerData).toEqual(null);
|
||||
});
|
||||
test("should have isSuppressedPart cleared", () => {
|
||||
// Arrange
|
||||
const answer = {
|
||||
answerResult: "DD11132",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "Yes",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [],
|
||||
answerData: "testAnswerData1",
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [],
|
||||
answerData: "testAnswerData2",
|
||||
isSuppressedPart: true,
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.questionsData[1].isSuppressedPart).toEqual(null);
|
||||
});
|
||||
test("should set answerSelected for each glass part question to null ", () => {
|
||||
// Arrange
|
||||
const answer = {
|
||||
answerResult: "DD11132",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "Yes",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [],
|
||||
answerData: "testAnswerData1",
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
answerSelected: "456",
|
||||
},
|
||||
{
|
||||
questionSequence: 2,
|
||||
questionText:
|
||||
"Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "FW04848",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "FW04846",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
answerSelected: "789",
|
||||
},
|
||||
],
|
||||
answerData: "testAnswerData2",
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answer, "", wrapper.vm);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.questionsData[1].questions[0].answerSelected).toEqual(null);
|
||||
expect(wrapper.vm.questionsData[1].questions[1].answerSelected).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("if there is a duplicate question", () => {
|
||||
test("then the glass piece's key should be updated", () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
key: "testkey1",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
key: "testkey2",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
const glassWithDuplicate = wrapper.vm.questionsData[1];
|
||||
|
||||
// Assert
|
||||
expect(glassWithDuplicate.key).not.toBe("testkey2");
|
||||
});
|
||||
|
||||
test("then that question should be supressed", () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
const duplicateQuestion = wrapper.vm.questionsData[1].questions[0];
|
||||
|
||||
// Assert
|
||||
expect(duplicateQuestion.suppressThisQuestion).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("and the duplicate has a nextQuestionSequence value", () => {
|
||||
test("then the question set as nextQuestionSequence should not be suppressed", () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "ZZZTest duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: 2,
|
||||
},
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 2,
|
||||
questionText: "Test question 2?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 3,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "345",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
suppressThisQuestion: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
const nextQuestionAfterDuplicate = wrapper.vm.questionsData[1].questions[2];
|
||||
|
||||
// Assert
|
||||
expect(nextQuestionAfterDuplicate.suppressThisQuestion).not.toBeTruthy();
|
||||
});
|
||||
|
||||
test("then the nextQuestionSequence should be updated and the original value saved", () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test question 3?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: 2,
|
||||
},
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 2,
|
||||
questionText: "Test question 2?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 3,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: 4,
|
||||
},
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 4,
|
||||
questionText: "Test question 4?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "567",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "678",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 5,
|
||||
questionText: "Test question 5?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "789",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "890",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
const duplicatedQuestion = wrapper.vm.questionsData[1].questions[2];
|
||||
const duplicatedQuestionAnswer = duplicatedQuestion.answers.filter((a) => {
|
||||
return a.selected;
|
||||
});
|
||||
const answerToTest = wrapper.vm.questionsData[1].questions[0].answers.filter(
|
||||
(a) => {
|
||||
return a.originalNextQuestionSequence;
|
||||
}
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(answerToTest[0].originalNextQuestionSequence).toEqual(
|
||||
duplicatedQuestion.questionSequence
|
||||
);
|
||||
expect(answerToTest[0].nextQuestionSequence).toEqual(
|
||||
duplicatedQuestionAnswer[0].nextQuestionSequence
|
||||
);
|
||||
});
|
||||
|
||||
describe("and the duplicate was the first question for that part", () => {
|
||||
test("then any questions up to the nextQuestionSequence value should be marked as suppressed", () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test duplicate question 1?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test duplicate question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: 2,
|
||||
},
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 2,
|
||||
questionText: "Test question 2?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 3,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "345",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
wrapper.vm.questionsData[1].questions[0].suppressThisQuestion
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper.vm.questionsData[1].questions[1].suppressThisQuestion
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper.vm.questionsData[1].questions[2].suppressThisQuestion
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the duplicate has an answerResult", () => {
|
||||
test("then any questions in the same glass piece that lead to the duplicate should be modified to just provide the duplicate's answerResult", async () => {
|
||||
// Arrange
|
||||
const answerNo = {
|
||||
answerResult: "456",
|
||||
answeredQuestions: [
|
||||
{
|
||||
questionText: "Test question 3?",
|
||||
selectedAnswerText: "No",
|
||||
questionNum: 1,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
};
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.questionsData = [
|
||||
{
|
||||
glassLocation: "Windshield",
|
||||
glassName: "Single",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
glassLocation: "Driver",
|
||||
glassName: "Front",
|
||||
questions: [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: "Test question 1?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: 2,
|
||||
},
|
||||
{
|
||||
answerResult: "",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 2,
|
||||
questionText: "Test question 2?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "123",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "234",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
questionSequence: 3,
|
||||
questionText: "Test question 3?",
|
||||
answers: [
|
||||
{
|
||||
answerResult: "345",
|
||||
answerText: "Yes",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
{
|
||||
answerResult: "456",
|
||||
answerText: "No",
|
||||
nextQuestionSequence: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Act
|
||||
await wrapper.vm.handleAnswerUpdates(answerNo, "", wrapper.vm);
|
||||
|
||||
const questionsToTest = wrapper.vm.questionsData[1].questions;
|
||||
const answerLeadingToDuplicate = questionsToTest[0].answers[1];
|
||||
const duplicateQuestion = questionsToTest[2];
|
||||
const answerInDuplicateQuestion = duplicateQuestion.answers.filter((a) => {
|
||||
return a.selected;
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(answerLeadingToDuplicate.answerResult).toEqual(
|
||||
answerInDuplicateQuestion[0].answerResult
|
||||
);
|
||||
expect(answerLeadingToDuplicate.originalAnswerResult).toBeFalsy();
|
||||
expect(answerLeadingToDuplicate.nextQuestionSequence).toEqual(null);
|
||||
expect(answerLeadingToDuplicate.originalNextQuestionSequence).toEqual(
|
||||
duplicateQuestion.questionSequence
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("navigateForward", () => {
|
||||
describe("should go to parts-questions", () => {
|
||||
test("single glass location has part question => go to parts-questions", async () => {
|
||||
|
|
|
|||
|
|
@ -303,13 +303,13 @@ export const mutations = {
|
|||
state.applicationUser.saveSessionPromise = null;
|
||||
},
|
||||
// Misc Mutations
|
||||
updateStateWithOrderInformation(state, orderInformation) {
|
||||
state.order.referralNumber = orderInformation.referralNumber;
|
||||
state.order.referralDate = orderInformation.referralDate;
|
||||
state.order.referralCorrelationId = orderInformation.referralCorrelationId;
|
||||
state.order.eon = orderInformation.eon;
|
||||
updateStateWithOrderInformation(state, sessionInformation) {
|
||||
state.order.referralNumber = sessionInformation.order.referralNumber;
|
||||
state.order.referralDate = sessionInformation.order.referralDate;
|
||||
state.order.referralCorrelationId = sessionInformation.order.referralCorrelationId;
|
||||
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.VEHICLE_PARTS] = null;
|
||||
state.applicationUser.pageData[fmgPageValues.MOLDING_QUESTIONS] = null;
|
||||
|
|
@ -317,44 +317,51 @@ export const mutations = {
|
|||
}
|
||||
|
||||
state.order.vehicle = Object.assign(state.order.vehicle, {
|
||||
year: orderInformation.vehicle?.year,
|
||||
make: orderInformation.vehicle?.make,
|
||||
model: orderInformation.vehicle?.model,
|
||||
style: orderInformation.vehicle?.style,
|
||||
vin: orderInformation.vehicle?.vin,
|
||||
carId: orderInformation.vehicle?.carId,
|
||||
category: orderInformation.vehicle?.category,
|
||||
imageUrl: orderInformation.vehicle?.imageUrl,
|
||||
imageVifNumber: orderInformation.vehicle?.imageVifNumber,
|
||||
imageColor: orderInformation.vehicle?.imageVifColor,
|
||||
year: sessionInformation.order.vehicle?.year,
|
||||
make: sessionInformation.order.vehicle?.make,
|
||||
model: sessionInformation.order.vehicle?.model,
|
||||
style: sessionInformation.order.vehicle?.style,
|
||||
vin: sessionInformation.order.vehicle?.vin,
|
||||
carId: sessionInformation.order.vehicle?.carId,
|
||||
category: sessionInformation.order.vehicle?.category,
|
||||
imageUrl: sessionInformation.order.vehicle?.imageUrl,
|
||||
imageVifNumber: sessionInformation.order.vehicle?.imageVifNumber,
|
||||
imageColor: sessionInformation.order.vehicle?.imageVifColor,
|
||||
registration: {
|
||||
firstName: orderInformation.vehicle.registration.firstName,
|
||||
lastName: orderInformation.vehicle.registration.lastName,
|
||||
address: orderInformation.vehicle.registration.streetAddress,
|
||||
city: orderInformation.vehicle.registration.city,
|
||||
state: orderInformation.vehicle.registration.state,
|
||||
zipCode: orderInformation.vehicle.registration.zipCode,
|
||||
licensePlate: orderInformation.vehicle.registration.licensePlateNumber,
|
||||
firstName: sessionInformation.order.vehicle.registration.firstName,
|
||||
lastName: sessionInformation.order.vehicle.registration.lastName,
|
||||
address: sessionInformation.order.vehicle.registration.streetAddress,
|
||||
city: sessionInformation.order.vehicle.registration.city,
|
||||
state: sessionInformation.order.vehicle.registration.state,
|
||||
zipCode: sessionInformation.order.vehicle.registration.zipCode,
|
||||
licensePlate: sessionInformation.order.vehicle.registration.licensePlateNumber,
|
||||
},
|
||||
});
|
||||
|
||||
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace;
|
||||
state.order.damage.isRepair = orderInformation.damage.isRepair;
|
||||
state.order.damage.numberOfChips = orderInformation.damage.numberOfChips;
|
||||
state.order.damage.glassToReplace = sessionInformation.order.damage.glassToReplace;
|
||||
state.order.damage.isRepair = sessionInformation.order.damage.isRepair;
|
||||
state.order.damage.numberOfChips = sessionInformation.order.damage.numberOfChips;
|
||||
|
||||
state.order.lineItems.glassParts = orderInformation.parts;
|
||||
state.order.accountNumber = orderInformation.accountNumber;
|
||||
(state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress),
|
||||
(state.order.serviceLocation.city = orderInformation.serviceLocation.city),
|
||||
(state.order.serviceLocation.state = orderInformation.serviceLocation.state),
|
||||
(state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode);
|
||||
state.order.lineItems.glassParts = sessionInformation.order.lineItems.parts;
|
||||
state.order.accountNumber = sessionInformation.order.accountNumber;
|
||||
state.order.providerNumber = sessionInformation.order.providerNumber;
|
||||
(state.order.serviceLocation.address =
|
||||
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 = orderInformation.IsInsuranceOrder;
|
||||
state.order.payment.isInsurance = sessionInformation.order.payment.isInsurance;
|
||||
state.order.payment.insuranceCoverage.isVerified =
|
||||
orderInformation?.insuranceInfo.coverageVerified;
|
||||
sessionInformation?.order.payment.insuranceCoverage.isVerified;
|
||||
|
||||
state.order.customer.emailAddress = orderInformation.customer.emailAddress;
|
||||
state.applicationUser.experiments = orderInformation.experiments;
|
||||
state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress;
|
||||
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) {
|
||||
state.applicationUser.experiments = experiments;
|
||||
|
|
@ -984,17 +991,12 @@ export const actions = {
|
|||
},
|
||||
});
|
||||
},
|
||||
loadSession(context, { referralNumber, referralDate, referralCorrelationId, accountNumber }) {
|
||||
loadSession(context, { savedSessionId }) {
|
||||
return globalMethods
|
||||
.callHttpClient({
|
||||
method: endpoints.LoadSession.method,
|
||||
endpoint: endpoints.LoadSession.url,
|
||||
payload: {
|
||||
referralNumber: referralNumber?.toString(),
|
||||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId,
|
||||
accountNumber: accountNumber?.toString(),
|
||||
},
|
||||
endpoint:
|
||||
endpoints.LoadSession.url + "?savedSessionId=" + savedSessionId?.toString(),
|
||||
})
|
||||
.then((response) => {
|
||||
// Flatten location and name properties
|
||||
|
|
@ -1409,6 +1411,12 @@ function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
|||
|
||||
function convertGlassPieceNamingForApi(glassArray) {
|
||||
if (!glassArray) 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 = [];
|
||||
glassArray.forEach((glass) => {
|
||||
converted.push({
|
||||
|
|
|
|||
|
|
@ -203,34 +203,65 @@ describe("Mutations", () => {
|
|||
expect(storeState.applicationUser.pageData["vehicle-year"]).toEqual({});
|
||||
});
|
||||
|
||||
it("updateStateWithOrderInformation, should set order information in state", () => {
|
||||
it("updateStateWithSessonInformation, should set session information in state", () => {
|
||||
// Arrange
|
||||
const storeState = state;
|
||||
|
||||
// Act
|
||||
mutations.updateStateWithOrderInformation(storeState, {
|
||||
referralNumber: 123,
|
||||
referralDate: new Date().toUTCString(),
|
||||
referralCorrelationId: "xxx-xxx-xxx",
|
||||
vehicle: {
|
||||
year: "2019",
|
||||
make: "Acura",
|
||||
model: "ILX",
|
||||
style: "4 DOOR SEDAN",
|
||||
carId: "C0000001",
|
||||
category: "CAR",
|
||||
registration: {},
|
||||
order: {
|
||||
referralNumber: 123,
|
||||
referralDate: new Date().toUTCString(),
|
||||
referralCorrelationId: "xxx-xxx-xxx",
|
||||
vehicle: {
|
||||
year: "2019",
|
||||
make: "Acura",
|
||||
model: "ILX",
|
||||
style: "4 DOOR SEDAN",
|
||||
carId: "C0000001",
|
||||
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: {
|
||||
glassToReplace: ["Windshield"],
|
||||
isRepair: false,
|
||||
numberOfChips: 0,
|
||||
applicationUser: {
|
||||
experiments: [
|
||||
{
|
||||
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
|
||||
|
|
@ -640,9 +671,7 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
referralNumber: "123",
|
||||
referralDate: new Date().toUTCString(),
|
||||
referralCorrelationId: "xxx-xxx-xxx",
|
||||
savedSessionId: "",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -667,9 +696,7 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
referralNumber: "123",
|
||||
referralDate: new Date().toUTCString(),
|
||||
referralCorrelationId: "xxx-xxx-xxx",
|
||||
savedSessionId: "",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -694,9 +721,7 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
referralNumber: "123",
|
||||
referralDate: new Date().toUTCString(),
|
||||
referralCorrelationId: "xxx-xxx-xxx",
|
||||
savedSessionId: "",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -1,15 +1,42 @@
|
|||
html {
|
||||
.has-error {
|
||||
// START HOVER
|
||||
&.list-button-horizontal,
|
||||
&.list-button,
|
||||
&.list-card,
|
||||
&.list-button-horizontal {
|
||||
&.list-button.list-group,
|
||||
&.list-card {
|
||||
border: 1px solid $red;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
|
||||
.button-content {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include box-shadow-hover($red-200);
|
||||
z-index: 5;
|
||||
}
|
||||
input[type="radio"]:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-radio {
|
||||
&:hover {
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
@include box-shadow-hover($red-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-check-input {
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
}
|
||||
// END HOVER
|
||||
|
||||
&.list-button,
|
||||
&.list-card {
|
||||
color: $red;
|
||||
|
|
@ -22,27 +49,11 @@ html {
|
|||
box-shadow: 0 0 0 1px $red;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 4px $red-200;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
label {
|
||||
border: 1px solid $red;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
label:hover {
|
||||
box-shadow: 0px 0px 0px 4px $red-200;
|
||||
border-radius: 10px;
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
&.list-button-horizontal {
|
||||
color: $red;
|
||||
label {
|
||||
border: 1px solid $red;
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 4px $red-200;
|
||||
}
|
||||
}
|
||||
input[type="checkbox"]:focus + label,
|
||||
input[type="radio"]:focus + label {
|
||||
box-shadow: 0 0 1px $red;
|
||||
|
|
@ -75,23 +86,21 @@ html {
|
|||
}
|
||||
&.textbox-question,
|
||||
&.dropdown-question {
|
||||
input:hover {
|
||||
box-shadow: 0px 0px 0px 4px $red-200;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid $red;
|
||||
}
|
||||
input:focus {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
p {
|
||||
color: $red;
|
||||
}
|
||||
input,
|
||||
select {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 1px $red;
|
||||
border: 1px solid $red;
|
||||
&:focus {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 4px $red-200;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -4,3 +4,7 @@
|
|||
@mixin blue-gradient {
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
}
|
||||
|
||||
@mixin box-shadow-hover($color) {
|
||||
box-shadow: 0 0 0 4px $color;
|
||||
}
|
||||
|
|
|
|||
15
src/styles/shared-input-button-styles.scss
Normal file
15
src/styles/shared-input-button-styles.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.base-input-button {
|
||||
&:not(.has-error):hover {
|
||||
cursor: pointer;
|
||||
|
||||
&.list-button,
|
||||
&.list-button-horizontal,
|
||||
&.list-card {
|
||||
&:not(.selected) {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
@include box-shadow-hover($blue-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<baseInputButton
|
||||
v-bind="$props"
|
||||
:buttonWrapperClasses="[
|
||||
'list-group list-button-horizontal d-flex flex-column w-100',
|
||||
'list-group list-button-horizontal d-flex flex-column w-100 base-input-button',
|
||||
{ strong: isStrongStyling },
|
||||
]"
|
||||
v-model="selectedValue">
|
||||
|
|
@ -88,18 +88,6 @@ export default {
|
|||
width: 100%;
|
||||
color: $gray-600;
|
||||
|
||||
&:hover {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
cursor: pointer;
|
||||
z-index: 4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
+ p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||
v-model="selectedValue">
|
||||
<div
|
||||
:aria-label="buttonLabel"
|
||||
|
|
@ -60,66 +60,54 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
.loader {
|
||||
position: absolute;
|
||||
}
|
||||
&.list-button {
|
||||
outline: none;
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
position: static; //override bootstrap
|
||||
.loader {
|
||||
position: absolute;
|
||||
}
|
||||
.list-button {
|
||||
outline: none;
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
position: static; //override bootstrap
|
||||
|
||||
&:focus-visible + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + .list-button-content {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
}
|
||||
&:checked:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + .list-button-content p,
|
||||
&:checked + .list-button-content span {
|
||||
font-weight: 500;
|
||||
}
|
||||
&:checked + .list-button-content span:nth-child(2) {
|
||||
font-weight: 400;
|
||||
color: $gray-600;
|
||||
}
|
||||
&:focus-visible + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + .list-button-content {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
}
|
||||
&:checked:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + .list-button-content p,
|
||||
&:checked + .list-button-content span {
|
||||
font-weight: 500;
|
||||
}
|
||||
&:checked + .list-button-content span:nth-child(2) {
|
||||
font-weight: 400;
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
.list-button-content {
|
||||
color: $gray-600;
|
||||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $border-radius-lg;
|
||||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
.list-button-content {
|
||||
color: $gray-600;
|
||||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $border-radius-lg;
|
||||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
|
||||
span {
|
||||
&.small {
|
||||
font-size: 0.75rem;
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
+ p {
|
||||
display: none;
|
||||
span {
|
||||
&.small {
|
||||
font-size: 0.75rem;
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<baseInputButton
|
||||
v-bind="$props"
|
||||
:buttonWrapperClasses="[
|
||||
'list-card w-100 rounded-3 d-flex align-items-center h-100',
|
||||
'list-card w-100 rounded-3 d-flex align-items-center h-100 base-input-button',
|
||||
{ horizontal: isWide },
|
||||
]"
|
||||
v-model="selectedValue">
|
||||
|
|
@ -88,13 +88,6 @@ export default {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0px 0px 0px 4px $blue-300;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
position: absolute;
|
||||
|
|
@ -104,10 +97,6 @@ export default {
|
|||
display: block;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
buttonWrapperClasses="ui-radio form-check"
|
||||
buttonWrapperClasses="ui-radio form-check base-input-button"
|
||||
inputClasses="form-check-input"
|
||||
v-model="selectedValue">
|
||||
<div class="d-flex align-items-start form-check-label">
|
||||
|
|
@ -64,7 +64,7 @@ export default {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
.form-check-input {
|
||||
.form-check-input:not(:checked) {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue