CSR-748 Fix merge conflicts

This commit is contained in:
Katie 2022-11-09 15:45:27 -05:00
commit 8a343650e3
12 changed files with 1302 additions and 1195 deletions

View file

@ -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>

View file

@ -22,10 +22,13 @@
<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"
buttonText="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;
}
}
}

View file

@ -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,
};
}
},

View file

@ -0,0 +1,100 @@
<template>
<div :class="`page-container-grouped-styles questions-page`">
<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";
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");
},
},
components: {
funnelHeader,
vehicleBanner,
alert,
questionChain,
funnelSubHeader,
funnelFooter,
},
};
</script>
<style lang="scss">
.questions-page {
.question-text {
margin-bottom: 0.5rem;
span {
text-align: left;
}
}
}
</style>

View file

@ -53,7 +53,7 @@ describe("textboxQuestion.vue", () => {
expect(paragraph.attributes("class")).toContain("rounded-pill");
});
it.only("Should return text-center class", async () => {
it.only("Should return form-control class", async () => {
// Act
const wrapper = shallowMount(textboxQuestion, {
global: {
@ -70,7 +70,7 @@ describe("textboxQuestion.vue", () => {
// Assert
const paragraph = wrapper.find("input");
expect(paragraph.attributes("class")).toContain("text-center");
expect(paragraph.attributes("class")).toContain("form-control");
});
it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {

View file

@ -4,7 +4,6 @@
:for="inputId"
:aria-label="questionText"
class="form-label"
:class="[questionAlignment ? 'mx-auto' : '']"
v-html="labelText"></label>
<div class="input-wrapper" :class="[includeSearchIcon ? 'has-search-icon' : '']">
<input
@ -23,8 +22,8 @@
:class="[
hasIcon ? 'has-icon' : '',
iconRight ? 'icon-right' : '',
questionAlignment ? 'text-center' : '',
isRoundedPill ? 'rounded-pill' : '',
questionAlignment === 'center' ? 'text-center' : '',
cornerStyle === 'rounded' ? 'rounded-pill' : '',
]"
:validationRules="validationRules"
@change="handleChange"
@ -66,9 +65,9 @@ export default {
},
validationRules: String,
cmsWidgetName: String,
maxLength: String, // Left or center. Default is left
questionAlignment: String, // Left or center. Default is left
cornerStyle: String, // Left or center. Default is left
maxLength: String,
questionAlignment: String, // Left or center. Left is default.
cornerStyle: String, // Rounded or square. Square is default.
includeSearchIcon: Boolean,
},
setup(props) {
@ -107,12 +106,6 @@ export default {
};
},
computed: {
isRoundedPill() {
return this.cornerStyle === "rounded";
},
isQuestionAlignment() {
return this.questionAlignment === "text";
},
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},

View file

@ -1,60 +1,32 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles capability-questions">
<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"
@tempButtonClicked="() => handleTempButtonClicked(this)"
@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 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));
@ -83,10 +55,9 @@ export default {
},
data() {
return {
capabilityQuestionsData: store.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
.partsOrQuestions,
questionsData: [],
selectedAnswers: {},
currentQuestionChainIndex: 0,
currentGlassIndex: 0,
};
},
computed: {
@ -96,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);
},
@ -112,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
@ -145,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,
@ -162,33 +134,30 @@ 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;
}
@ -196,328 +165,10 @@ export default {
// TODO KO delete after quote MVP
this.navigateForward(partsOrQuestions, null, this.shouldGoToHeritageQuote);
},
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,
Form,
questionsPageLayout,
},
};
</script>
<style lang="scss">
.capability-questions {
.question-text {
margin-bottom: 0.5rem;
span {
text-align: left;
}
}
}
</style>

View file

@ -1,56 +1,27 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles molding-questions">
<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"
@tempButtonClicked="() => handleTempButtonClicked(this)"
@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 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";
@ -84,8 +55,7 @@ export default {
},
data() {
return {
moldingQuestionsData: store.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)
.partsOrQuestions,
questionsData: [],
selectedAnswers: {},
currentGlassIndex: 0,
};
@ -105,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: {
@ -125,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,
@ -143,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 = {};
});
@ -173,312 +150,10 @@ export default {
// TODO KO delete after quote MVP
this.navigateForward(partsOrQuestions, null, this.shouldGoToHeritageQuote);
},
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,
Form,
questionsPageLayout,
},
};
</script>
<style lang="scss">
.molding-questions {
.question-text {
margin-bottom: 0.5rem;
span {
text-align: left;
}
}
}
</style>

View file

@ -1,56 +1,27 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
<div class="page-container-grouped-styles part-questions">
<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"
@tempButtonClicked="() => handleTempButtonClicked(this)"
@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 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";
@ -62,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);
@ -83,7 +55,7 @@ export default {
},
data() {
return {
partsQuestionsData: [],
questionsData: [],
selectedAnswers: {},
currentGlassIndex: 0,
};
@ -99,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: {
@ -120,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,
@ -138,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 = {};
});
@ -150,322 +129,19 @@ export default {
);
// call API parts method
const partsLookup = await this.dispatchStoreAction(storeActions.GET_PARTS).catch(() => {
return this.$refs.funnelFooter.removeLoader();
});
const partsLookup = await this.dispatchStoreAction(this.storeActions.GET_PARTS);
const glassPiecePartsForStore = partsLookup.data.glassPieceParts;
const glassPartsForStore = partsLookup.data.glassPieceParts;
// this.navigateForward(glassPiecePartsForStore);
// TODO KO delete after quote MVP
this.navigateForward(glassPiecePartsForStore, null, this.shouldGoToHeritageQuote);
},
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)
// this.navigateForward(glassPartsForStore);
/*
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.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,
};
// 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;
}
}
// TODO KO delete for quote mvp
this.navigateForward(glassPartsForStore, null, this.shouldGoToHeritageQuote);
},
},
components: {
funnelHeader,
vehicleBanner,
alert,
questionChain,
funnelSubHeader,
funnelFooter,
Form,
questionsPageLayout,
},
};
</script>
<style lang="scss">
.part-questions {
.question-text {
margin-bottom: 0.5rem;
span {
text-align: left;
}
}
}
</style>

View file

@ -9,7 +9,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"

View file

@ -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";
@ -73,9 +72,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;
@ -100,33 +99,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
@ -141,20 +146,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;
}
}
},
// TODO KO Delete `shouldGoToHeritageQuote`
// Can't use `this` because navigateForward is also called from vin-pages-mixin
async navigateForward(partsOrQuestions, vm, shouldGoToHeritageQuote) {
@ -247,6 +453,7 @@ 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);

View file

@ -335,24 +335,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
@ -428,6 +410,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 () => {