Merge pull request #3272 from Safelite/feature/CASH-3034
CASH-3034- part-questions/Capability questions/Moulding questions UI …
This commit is contained in:
commit
f521d54237
14 changed files with 626 additions and 92 deletions
|
|
@ -109,11 +109,11 @@ describe("Question Chain component", () => {
|
||||||
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
|
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should NOT emit update:modelValue if returnedAnswer contains 'nextQuestion'", () => {
|
test("should not emit update:modelValue for nextQuestion during normal forward progress", () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
const testQuestion = {};
|
const testQuestion = {};
|
||||||
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
|
const testReturnedAnswer = "1|nextQuestion|3|No";
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
|
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
|
||||||
|
|
@ -121,6 +121,24 @@ describe("Question Chain component", () => {
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
|
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should emit update:modelValue for nextQuestion when revising a saved answer", async () => {
|
||||||
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ hasSavedAnswer: true });
|
||||||
|
const testQuestion = {};
|
||||||
|
const testReturnedAnswer = "1|nextQuestion|3|No";
|
||||||
|
|
||||||
|
//Act
|
||||||
|
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0][0]).toMatchObject({
|
||||||
|
incomplete: true,
|
||||||
|
index: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("method getQuestionChainAnswerIfComplete...", () => {
|
describe("method getQuestionChainAnswerIfComplete...", () => {
|
||||||
|
|
@ -213,25 +231,103 @@ describe("Question Chain component", () => {
|
||||||
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined;
|
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return false if returnedAnswer is a nextQuestion (not a final answer)", async () => {
|
test("should follow the No path and show the next question when changing Q1 from Yes to No", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
questionDataProp: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Is this the Overland edition?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: null,
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: null,
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 2,
|
||||||
|
questionText: "Rain sensing wipers?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "PART-A", answerText: "Yes", nextQuestionSequence: 3 },
|
||||||
|
{ answerResult: "PART-B", answerText: "No", nextQuestionSequence: 3 },
|
||||||
|
],
|
||||||
|
answerSelected: "2|answer|PART-A|Yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 3,
|
||||||
|
questionText: "Laminated door glass?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "PART-C",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: "PART-D",
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerSelected: "3|answer|PART-C|Yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 5,
|
||||||
|
questionText: "Factory installed antenna?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "PART-E",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: "PART-F",
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await wrapper.setProps({ index: 0 });
|
||||||
|
|
||||||
|
const result = wrapper.vm.getQuestionChainAnswerIfComplete("1|nextQuestion|5|No");
|
||||||
|
|
||||||
|
expect(result.incomplete).toBe(true);
|
||||||
|
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined();
|
||||||
|
expect(wrapper.vm.questions[2].answerSelected).toBeUndefined();
|
||||||
|
expect(wrapper.vm.currentQuestionNum).toBe(5);
|
||||||
|
expect(result.answeredQuestions).toHaveLength(1);
|
||||||
|
expect(result.answeredQuestions[0].selectedAnswerText).toBe("No");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return incomplete answer if returnedAnswer is a nextQuestion (not a final answer)", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
|
const testReturnedAnswer = "1|nextQuestion|3|No";
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(result).toBeFalsy();
|
expect(result).toMatchObject({
|
||||||
|
incomplete: true,
|
||||||
|
index: 0,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => {
|
test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
|
const testReturnedAnswer = "1|nextQuestion|3|No";
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||||
|
|
@ -323,9 +419,129 @@ describe("Question Chain component", () => {
|
||||||
expect(wrapper.vm.questions[0].answers[0].problemQuestionId).toBe(9531);
|
expect(wrapper.vm.questions[0].answers[0].problemQuestionId).toBe(9531);
|
||||||
expect(wrapper.vm.questions[0].answers[1].problemQuestionId).toBe(9529);
|
expect(wrapper.vm.questions[0].answers[1].problemQuestionId).toBe(9529);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should normalize uppercase answerSelected to match button value on created", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
questionDataProp: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Is your vehicle equipped with rain sensing wipers?",
|
||||||
|
answerSelected: "1|ANSWER|DW01705|YES",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "DW01705",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: "DW01591",
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.questions[0].answerSelected).toBe("1|answer|DW01705|Yes");
|
||||||
|
});
|
||||||
|
test("should show question 3 after answering No on Cherokee Overland question", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
questionDataProp: getCherokeeWindshieldQuestions(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const q1 = wrapper.vm.questions[0];
|
||||||
|
wrapper.vm.handleAnswer(q1, "1|nextQuestion|3|No");
|
||||||
|
|
||||||
|
expect(wrapper.vm.currentQuestionNum).toBe(3);
|
||||||
|
expect(
|
||||||
|
wrapper.vm.questions.filter((question) => wrapper.vm.isQuestionVisible(question))
|
||||||
|
).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ questionSequence: 1 }),
|
||||||
|
expect.objectContaining({ questionSequence: 3 }),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper.vm.questions.filter((question) => wrapper.vm.isQuestionVisible(question))
|
||||||
|
).not.toEqual(
|
||||||
|
expect.arrayContaining([expect.objectContaining({ questionSequence: 2 })])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should resume at question 3 after remount when Q1 No was already saved", async () => {
|
||||||
|
const questionDataProp = getCherokeeWindshieldQuestions();
|
||||||
|
questionDataProp[0].answerSelected = "1|nextQuestion|3|No";
|
||||||
|
|
||||||
|
const { wrapper } = setupMocks({ questionDataProp });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.currentQuestionNum).toBe(3);
|
||||||
|
expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[2])).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getCherokeeWindshieldQuestions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText:
|
||||||
|
"Is your Cherokee the Overland edition which can be identified by having a wood and leather wrapped steering wheel?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "", answerText: "Yes", nextQuestionSequence: 2 },
|
||||||
|
{ answerResult: "", answerText: "No", nextQuestionSequence: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 2,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "DW02270", answerText: "Yes", nextQuestionSequence: null },
|
||||||
|
{ answerResult: "DW02264", answerText: "No", nextQuestionSequence: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 3,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with rain sensing wipers that adjust their speed automatically when it rains?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "DW02268", answerText: "Yes", nextQuestionSequence: null },
|
||||||
|
{ answerResult: "", answerText: "No", nextQuestionSequence: 4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 4,
|
||||||
|
questionText:
|
||||||
|
"Is your vehicle equipped with automatic climate control which will change the fan speed automatically in order to maintain a set temperature?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "", answerText: "Yes", nextQuestionSequence: 5 },
|
||||||
|
{ answerResult: "", answerText: "No", nextQuestionSequence: 6 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 5,
|
||||||
|
questionText: "Is your vehicle equipped with heated seats?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "DW02104", answerText: "Yes", nextQuestionSequence: null },
|
||||||
|
{ answerResult: "DW02103", answerText: "No", nextQuestionSequence: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 6,
|
||||||
|
questionText: "Is your vehicle equipped with heated seats?",
|
||||||
|
answers: [
|
||||||
|
{ answerResult: "DW02102", answerText: "Yes", nextQuestionSequence: null },
|
||||||
|
{ answerResult: "DW02101", answerText: "No", nextQuestionSequence: null },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function setupMocks({
|
function setupMocks({
|
||||||
questionDataProp = [
|
questionDataProp = [
|
||||||
{
|
{
|
||||||
|
|
@ -351,6 +567,7 @@ function setupMocks({
|
||||||
const mountOptions = getMountOptions({});
|
const mountOptions = getMountOptions({});
|
||||||
mountOptions.propsData = {
|
mountOptions.propsData = {
|
||||||
questionData: questionDataProp,
|
questionData: questionDataProp,
|
||||||
|
index: 0,
|
||||||
};
|
};
|
||||||
mountOptions["attachTo"] = document.body;
|
mountOptions["attachTo"] = document.body;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
<div v-for="(q, i) in questions" :key="i">
|
<div v-for="(q, i) in questions" :key="i">
|
||||||
<transition appear name="fade" mode="out-in">
|
<transition appear name="fade" mode="out-in">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
v-if="q.answerSelected || q.questionSequence === currentQuestionNum"
|
v-if="isQuestionVisible(q)"
|
||||||
class="radioQuestion"
|
class="radioQuestion"
|
||||||
:class="q.questionSequence === currentQuestionNum && 'current-question'"
|
:class="isCurrentQuestion(q) && 'current-question'"
|
||||||
:questionText="q.questionText"
|
:questionText="q.questionText"
|
||||||
:answers="q.answers"
|
:answers="q.answers"
|
||||||
:groupName="`question-${index}-${q.questionSequence}`"
|
:groupName="`question-${index}-${q.questionSequence}`"
|
||||||
|
|
@ -19,6 +19,10 @@
|
||||||
<script>
|
<script>
|
||||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||||
import { useValidateForm } from "vee-validate";
|
import { useValidateForm } from "vee-validate";
|
||||||
|
import {
|
||||||
|
mapQuestionAnswersForChain,
|
||||||
|
normalizeAnswerSelectedValue,
|
||||||
|
} from "@/helpers/question-chain-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "questionChain",
|
name: "questionChain",
|
||||||
|
|
@ -34,54 +38,81 @@ export default {
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
index: Number,
|
index: Number,
|
||||||
answerKey: String,
|
answerKey: String,
|
||||||
|
hasSavedAnswer: Boolean,
|
||||||
|
hasDownstreamSavedAnswers: Boolean,
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
// do a test validation check upon create to prevent out of sync / incorrect valid states
|
// do a test validation check upon create to prevent out of sync / incorrect valid states
|
||||||
await useValidateForm(); // NOTE: needs to have async/await here; tested and won't work without it
|
await useValidateForm(); // NOTE: needs to have async/await here; tested and won't work without it
|
||||||
|
|
||||||
this.questionData.map((q, i) => {
|
this.questionData.map((q, i) => {
|
||||||
|
const answers = mapQuestionAnswersForChain(q);
|
||||||
const question = {
|
const question = {
|
||||||
questionText: q.questionText,
|
questionText: q.questionText,
|
||||||
questionSequence: q.questionSequence,
|
questionSequence: q.questionSequence,
|
||||||
answers: q.answers.map((a) => {
|
answers,
|
||||||
return {
|
answerSelected: normalizeAnswerSelectedValue(q.answerSelected, answers),
|
||||||
buttonLabel: a.answerText,
|
|
||||||
// Name will either be nextQuestionSequence or answerResult
|
|
||||||
// Name will be used by list-button as the input value.
|
|
||||||
// It must be a single string or number, so concatenating together a string with
|
|
||||||
// 4 pieces of data separated by pipe characters:
|
|
||||||
// question number|type of answer|answer value|answer text
|
|
||||||
value: a.nextQuestionSequence
|
|
||||||
? q.questionSequence +
|
|
||||||
"|nextQuestion|" +
|
|
||||||
a.nextQuestionSequence +
|
|
||||||
"|" +
|
|
||||||
a.answerText
|
|
||||||
: q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText,
|
|
||||||
nextQuestionSequence: a.nextQuestionSequence,
|
|
||||||
answerResult: a.answerResult,
|
|
||||||
problemQuestionId: a.problemQuestionId,
|
|
||||||
questionSequence: q.questionSequence,
|
|
||||||
questionType: a.nextQuestionSequence ? "nextQuestion" : "answer",
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
answerSelected: q.answerSelected || "",
|
|
||||||
};
|
};
|
||||||
if (!q.suppressThisQuestion) {
|
if (!q.suppressThisQuestion) {
|
||||||
this.questions.push(question);
|
this.questions.push(question);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.modelValue?.length > 0 && this.questions.length > 0) {
|
if (this.questions.length > 0) {
|
||||||
// set this.currentQuestionNum to first valid question
|
this.initializeCurrentQuestionNum();
|
||||||
this.currentQuestionNum = this.questions[0].questionSequence;
|
this.scrollCurrentQuestionIntoView();
|
||||||
// scroll the next question into view
|
|
||||||
this.$nextTick(() => {
|
|
||||||
document.querySelector(".current-question")?.scrollIntoView({ behavior: "smooth" });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getQuestionSequence(question) {
|
||||||
|
return Number(question?.questionSequence);
|
||||||
|
},
|
||||||
|
isCurrentQuestion(question) {
|
||||||
|
return this.getQuestionSequence(question) === Number(this.currentQuestionNum);
|
||||||
|
},
|
||||||
|
isQuestionVisible(question) {
|
||||||
|
return !!question.answerSelected || this.isCurrentQuestion(question);
|
||||||
|
},
|
||||||
|
initializeCurrentQuestionNum() {
|
||||||
|
const pendingNextQuestion = [...this.questions]
|
||||||
|
.reverse()
|
||||||
|
.find((question) => question.answerSelected?.includes("|nextQuestion|"));
|
||||||
|
|
||||||
|
if (pendingNextQuestion) {
|
||||||
|
const nextQuestionSequence = Number(
|
||||||
|
pendingNextQuestion.answerSelected.split("|")[2]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.questions.some(
|
||||||
|
(question) => this.getQuestionSequence(question) === nextQuestionSequence
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.currentQuestionNum = nextQuestionSequence;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstUnanswered = this.questions.find((question) => !question.answerSelected);
|
||||||
|
|
||||||
|
if (firstUnanswered) {
|
||||||
|
this.currentQuestionNum = this.getQuestionSequence(firstUnanswered);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollCurrentQuestionIntoView() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document.querySelector(".current-question")?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
syncAnswerSelectedToQuestionData(questionSequence, answerSelected) {
|
||||||
|
const sourceQuestion = this.questionData?.find(
|
||||||
|
(q) => this.getQuestionSequence(q) === Number(questionSequence)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sourceQuestion) {
|
||||||
|
sourceQuestion.answerSelected = answerSelected;
|
||||||
|
}
|
||||||
|
},
|
||||||
getProblemQuestionIdFromSelectedAnswer(question, selectedAnswer) {
|
getProblemQuestionIdFromSelectedAnswer(question, selectedAnswer) {
|
||||||
if (!question?.answers || !selectedAnswer) {
|
if (!question?.answers || !selectedAnswer) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -100,10 +131,20 @@ export default {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
question.answerSelected = returnedAnswer;
|
question.answerSelected = returnedAnswer;
|
||||||
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer);
|
this.syncAnswerSelectedToQuestionData(question.questionSequence, returnedAnswer);
|
||||||
|
const questionChainAnswer = this.getQuestionChainAnswerIfComplete(returnedAnswer);
|
||||||
|
|
||||||
if (isQuestionChainComplete) {
|
if (!questionChainAnswer) {
|
||||||
this.$emit("update:modelValue", isQuestionChainComplete);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldNotifyParent =
|
||||||
|
!questionChainAnswer.incomplete ||
|
||||||
|
this.hasSavedAnswer ||
|
||||||
|
this.hasDownstreamSavedAnswers;
|
||||||
|
|
||||||
|
if (shouldNotifyParent) {
|
||||||
|
this.$emit("update:modelValue", questionChainAnswer);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getQuestionChainAnswerIfComplete(returnedAnswer) {
|
getQuestionChainAnswerIfComplete(returnedAnswer) {
|
||||||
|
|
@ -117,20 +158,24 @@ export default {
|
||||||
// "5|answer|DW02104|Yes"
|
// "5|answer|DW02104|Yes"
|
||||||
|
|
||||||
const returnedAnswerArray = returnedAnswer.split("|");
|
const returnedAnswerArray = returnedAnswer.split("|");
|
||||||
const questionNum = parseInt(returnedAnswerArray[0]);
|
const questionNum = Number(returnedAnswerArray[0]);
|
||||||
const questionType = returnedAnswerArray[1];
|
const questionType = returnedAnswerArray[1];
|
||||||
const questionAnswer = returnedAnswerArray[2];
|
const questionAnswer = returnedAnswerArray[2];
|
||||||
const answeredQuestions = [];
|
const answeredQuestions = [];
|
||||||
|
|
||||||
this.questions.forEach((q) => {
|
this.questions.forEach((q) => {
|
||||||
|
const questionSequence = this.getQuestionSequence(q);
|
||||||
|
|
||||||
// find this question and mark it as "answered" by populating answerSelected
|
// find this question and mark it as "answered" by populating answerSelected
|
||||||
if (q.questionSequence === questionNum) {
|
if (questionSequence === questionNum) {
|
||||||
q.answerSelected = returnedAnswer;
|
q.answerSelected = returnedAnswer;
|
||||||
|
this.syncAnswerSelectedToQuestionData(questionSequence, returnedAnswer);
|
||||||
}
|
}
|
||||||
// remove all answers AFTER this question...
|
// remove all answers AFTER this question...
|
||||||
// (needed in case user is changing previously answered questions)
|
// (needed in case user is changing previously answered questions)
|
||||||
if (q.questionSequence > questionNum) {
|
if (questionSequence > questionNum) {
|
||||||
delete q.answerSelected;
|
delete q.answerSelected;
|
||||||
|
this.syncAnswerSelectedToQuestionData(questionSequence, "");
|
||||||
}
|
}
|
||||||
if (q.answerSelected) {
|
if (q.answerSelected) {
|
||||||
answeredQuestions.push({
|
answeredQuestions.push({
|
||||||
|
|
@ -146,22 +191,21 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// return false if there's a nextQuestion... or return an object with final answers (truthy)
|
// return incomplete answer if there's a nextQuestion... or return final answers (truthy)
|
||||||
if (questionType === "nextQuestion") {
|
if (questionType === "nextQuestion") {
|
||||||
// update to next question index
|
// update to next question index
|
||||||
this.currentQuestionNum = parseInt(questionAnswer); // update count to display next question
|
this.currentQuestionNum = Number(questionAnswer);
|
||||||
// scroll the next question into view
|
this.scrollCurrentQuestionIntoView();
|
||||||
this.$nextTick(() => {
|
return {
|
||||||
document
|
incomplete: true,
|
||||||
.querySelector(".current-question")
|
answeredQuestions,
|
||||||
.scrollIntoView({ behavior: "smooth" });
|
index: this.index,
|
||||||
});
|
};
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
// reset current question index (removes .current-question class)
|
// reset current question index (removes .current-question class)
|
||||||
this.currentQuestionNum = 0; // reset count
|
this.currentQuestionNum = 0; // reset count
|
||||||
const answeredQuestion = this.questions.find(
|
const answeredQuestion = this.questions.find(
|
||||||
(q) => q.questionSequence === questionNum
|
(q) => this.getQuestionSequence(q) === questionNum
|
||||||
);
|
);
|
||||||
|
|
||||||
// return an object with the part answer, all the answered questions, and the part index
|
// return an object with the part answer, all the answered questions, and the part index
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,43 @@ describe("questionsPageLayout.vue", () => {
|
||||||
|
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Should return false when answerData was cleared even if answerSelected remains", async () => {
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 1 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Is your Grand Cherokee the Laredo model?",
|
||||||
|
answers: [],
|
||||||
|
answerSelected: "1|answer|DD11132|Yes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerData: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(wrapper.vm.showThisQuestionChain(testGlassPiece, 0)).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return false for reset chains ahead of the current index", async () => {
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
await wrapper.setProps({ index: 1 });
|
||||||
|
|
||||||
|
const testGlassPiece = {
|
||||||
|
questions: [
|
||||||
|
{ questionSequence: 1, questionText: "Rain sensing wipers?", answers: [] },
|
||||||
|
],
|
||||||
|
answerData: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(wrapper.vm.showThisQuestionChain(testGlassPiece, 2)).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("method handleForwardButtonAction...", () => {
|
describe("method handleForwardButtonAction...", () => {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7 question-chain-wrapper">
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7 question-chain-wrapper">
|
||||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
<div
|
||||||
|
v-for="(questionsDatum, i) in questionsData"
|
||||||
|
:key="questionsDatum.key ?? questionsDatum.answerKey ?? i">
|
||||||
<questionChain
|
<questionChain
|
||||||
ref="questionChain"
|
ref="questionChain"
|
||||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||||
|
|
@ -18,10 +20,12 @@
|
||||||
:index="i"
|
:index="i"
|
||||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||||
:answerKey="questionsDatum.answerKey"
|
:answerKey="questionsDatum.answerKey"
|
||||||
|
:hasSavedAnswer="!!questionsDatum.answerData?.answerResult"
|
||||||
|
:hasDownstreamSavedAnswers="hasDownstreamSavedAnswers(i)"
|
||||||
:validationRules="validationRules" />
|
:validationRules="validationRules" />
|
||||||
<!-- Save your progress slot -->
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Save your progress slot (once per page, not per glass piece) -->
|
||||||
|
<slot></slot>
|
||||||
|
|
||||||
<navbar
|
<navbar
|
||||||
ref="navbar"
|
ref="navbar"
|
||||||
|
|
@ -60,12 +64,22 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
hasDownstreamSavedAnswers(glassIndex) {
|
||||||
|
return this.questionsData
|
||||||
|
?.slice(glassIndex + 1)
|
||||||
|
.some((glass) => glass.answerData?.answerResult);
|
||||||
|
},
|
||||||
showThisQuestionChain(glass, i) {
|
showThisQuestionChain(glass, i) {
|
||||||
// return false if no questions or if suppressed
|
|
||||||
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
if (!glass.questions || glass.questions?.length < 1 || glass.isSuppressedPart) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.index === i || glass.answerData?.answerResult?.length > 0;
|
|
||||||
|
if (this.index === i) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show completed chains that still have a saved answer
|
||||||
|
return !!glass.answerData?.answerResult;
|
||||||
},
|
},
|
||||||
handleForwardButtonAction() {
|
handleForwardButtonAction() {
|
||||||
this.$emit("forwardButtonAction");
|
this.$emit("forwardButtonAction");
|
||||||
|
|
|
||||||
49
src/helpers/question-chain-helper.js
Normal file
49
src/helpers/question-chain-helper.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* Builds the pipe-delimited value used by question-chain button options.
|
||||||
|
* Format: questionNumber|type|answer value|answer text
|
||||||
|
*/
|
||||||
|
export function buildQuestionChainAnswerValue({
|
||||||
|
questionSequence,
|
||||||
|
nextQuestionSequence,
|
||||||
|
answerResult,
|
||||||
|
answerText,
|
||||||
|
}) {
|
||||||
|
if (nextQuestionSequence) {
|
||||||
|
return `${questionSequence}|nextQuestion|${nextQuestionSequence}|${answerText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${questionSequence}|answer|${answerResult}|${answerText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapQuestionAnswersForChain(question) {
|
||||||
|
return question.answers.map((answer) => ({
|
||||||
|
buttonLabel: answer.answerText,
|
||||||
|
value: buildQuestionChainAnswerValue({
|
||||||
|
questionSequence: question.questionSequence,
|
||||||
|
nextQuestionSequence: answer.nextQuestionSequence,
|
||||||
|
answerResult: answer.answerResult,
|
||||||
|
answerText: answer.answerText,
|
||||||
|
}),
|
||||||
|
nextQuestionSequence: answer.nextQuestionSequence,
|
||||||
|
answerResult: answer.answerResult,
|
||||||
|
problemQuestionId: answer.problemQuestionId,
|
||||||
|
questionSequence: question.questionSequence,
|
||||||
|
questionType: answer.nextQuestionSequence ? "nextQuestion" : "answer",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAnswerSelectedValue(answerSelected, answers) {
|
||||||
|
if (!answerSelected) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answers.some((answer) => answer.value === answerSelected)) {
|
||||||
|
return answerSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchedAnswer = answers.find(
|
||||||
|
(answer) => answer.value.toUpperCase() === String(answerSelected).toUpperCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchedAnswer?.value ?? answerSelected;
|
||||||
|
}
|
||||||
74
src/helpers/question-chain-helper.spec.js
Normal file
74
src/helpers/question-chain-helper.spec.js
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import {
|
||||||
|
buildQuestionChainAnswerValue,
|
||||||
|
mapQuestionAnswersForChain,
|
||||||
|
normalizeAnswerSelectedValue,
|
||||||
|
} from "./question-chain-helper";
|
||||||
|
|
||||||
|
describe("question-chain-helper", () => {
|
||||||
|
describe("buildQuestionChainAnswerValue", () => {
|
||||||
|
test("returns nextQuestion format when nextQuestionSequence is set", () => {
|
||||||
|
expect(
|
||||||
|
buildQuestionChainAnswerValue({
|
||||||
|
questionSequence: 1,
|
||||||
|
nextQuestionSequence: 3,
|
||||||
|
answerResult: "ignored",
|
||||||
|
answerText: "Yes",
|
||||||
|
})
|
||||||
|
).toBe("1|nextQuestion|3|Yes");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns answer format for final answers", () => {
|
||||||
|
expect(
|
||||||
|
buildQuestionChainAnswerValue({
|
||||||
|
questionSequence: 1,
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
answerResult: "DW01705",
|
||||||
|
answerText: "Yes",
|
||||||
|
})
|
||||||
|
).toBe("1|answer|DW01705|Yes");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("normalizeAnswerSelectedValue", () => {
|
||||||
|
const answers = [{ value: "1|answer|DW01705|Yes" }, { value: "1|answer|DW01591|No" }];
|
||||||
|
|
||||||
|
test("returns empty string when answerSelected is empty", () => {
|
||||||
|
expect(normalizeAnswerSelectedValue("", answers)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns exact match when casing already matches", () => {
|
||||||
|
expect(normalizeAnswerSelectedValue("1|answer|DW01705|Yes", answers)).toBe(
|
||||||
|
"1|answer|DW01705|Yes"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normalizes uppercase legacy values to button value casing", () => {
|
||||||
|
expect(normalizeAnswerSelectedValue("1|ANSWER|DW01705|YES", answers)).toBe(
|
||||||
|
"1|answer|DW01705|Yes"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("mapQuestionAnswersForChain", () => {
|
||||||
|
test("maps API answers to question-chain button answers", () => {
|
||||||
|
const result = mapQuestionAnswersForChain({
|
||||||
|
questionSequence: 1,
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "DW01705",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
problemQuestionId: 123,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result[0]).toMatchObject({
|
||||||
|
buttonLabel: "Yes",
|
||||||
|
value: "1|answer|DW01705|Yes",
|
||||||
|
problemQuestionId: 123,
|
||||||
|
questionType: "answer",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -213,7 +213,7 @@ describe("capabilityQuestions.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("forwardButtonAction", () => {
|
describe("forwardButtonAction", () => {
|
||||||
test("Should clear out answerData", () => {
|
test("Should clear out answerData", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
|
@ -245,7 +245,7 @@ describe("capabilityQuestions.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
||||||
|
|
|
||||||
|
|
@ -180,11 +180,6 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// clear out answerData for future page loads; must occur prior to store save
|
|
||||||
this.questionsData.forEach((glass) => {
|
|
||||||
glass.answerData = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
// save to vuex store as order.damage.capabilityQuestionAnswers (array)
|
// save to vuex store as order.damage.capabilityQuestionAnswers (array)
|
||||||
// used in GET_PART_FROM_CAPABILITY_QUESTION_ANSWER call following this one
|
// used in GET_PART_FROM_CAPABILITY_QUESTION_ANSWER call following this one
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
|
|
@ -211,6 +206,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.navigateForward(partsOrQuestions);
|
this.navigateForward(partsOrQuestions);
|
||||||
|
|
||||||
|
this.clearQuestionsAnswerData();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ describe("moldingQuestions.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("forwardButtonAction", () => {
|
describe("forwardButtonAction", () => {
|
||||||
test("Should clear out answerData", () => {
|
test("Should clear out answerData", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
|
@ -240,7 +240,7 @@ describe("moldingQuestions.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
||||||
|
|
|
||||||
|
|
@ -181,11 +181,6 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// clear out answerData for future page loads; must occur prior to store save
|
|
||||||
this.questionsData.forEach((glass) => {
|
|
||||||
glass.answerData = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
// save to vuex store as order.damage.moldingQuestionArrays (array)
|
// save to vuex store as order.damage.moldingQuestionArrays (array)
|
||||||
await this.dispatchStoreAction(
|
await this.dispatchStoreAction(
|
||||||
this.storeActions.SAVE_MOLDING_QUESTION_ANSWERS,
|
this.storeActions.SAVE_MOLDING_QUESTION_ANSWERS,
|
||||||
|
|
@ -209,6 +204,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.navigateForward(partsOrQuestions);
|
this.navigateForward(partsOrQuestions);
|
||||||
|
|
||||||
|
this.clearQuestionsAnswerData();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -258,9 +258,7 @@ describe("partQuestions.vue...", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
expect(wrapper.vm.questionsData[0].answerData).toEqual({});
|
||||||
|
|
|
||||||
|
|
@ -182,13 +182,6 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// clear out answerData for future page loads; must occur prior to store save
|
|
||||||
this.questionsData.forEach((glass) => {
|
|
||||||
if (glass.answerData) {
|
|
||||||
glass.answerData = {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
|
this.dispatchStoreAction(this.storeActions.SAVE_IS_OEM_GLASS_SELECTED, false, false);
|
||||||
|
|
||||||
// save to vuex store as order.damage.partQuestionAnswers (array)
|
// save to vuex store as order.damage.partQuestionAnswers (array)
|
||||||
|
|
@ -209,6 +202,8 @@ export default {
|
||||||
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
||||||
|
|
||||||
this.navigateForward(glassPartsForStore);
|
this.navigateForward(glassPartsForStore);
|
||||||
|
|
||||||
|
this.clearQuestionsAnswerData();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,17 @@ import baseMixin from "@/mixins/base-mixin.js";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { getIsWindshieldOnly } from "@/helpers/damage-helper";
|
import { getIsWindshieldOnly } from "@/helpers/damage-helper";
|
||||||
import { partTypeStrings } from "@/constants/part-type-strings";
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
||||||
|
import { buildQuestionChainAnswerValue } from "@/helpers/question-chain-helper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
|
clearQuestionsAnswerData() {
|
||||||
|
this.questionsData?.forEach((glass) => {
|
||||||
|
if (glass.answerData) {
|
||||||
|
glass.answerData = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
hasPartQuestions(partsOrQuestions) {
|
hasPartQuestions(partsOrQuestions) {
|
||||||
return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0);
|
return partsOrQuestions?.some((pq) => pq.partQuestions?.length > 0);
|
||||||
},
|
},
|
||||||
|
|
@ -114,11 +122,12 @@ export default {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// set the answerString to use for answerSelected
|
// set the answerString to use for answerSelected
|
||||||
if (chosenAns.nextQuestionSequence) {
|
answerString = buildQuestionChainAnswerValue({
|
||||||
answerString = `${answeredQuestion.questionNum}|nextQuestion|${chosenAns.nextQuestionSequence}|${chosenAns.answerText}`;
|
questionSequence: answeredQuestion.questionNum,
|
||||||
} else {
|
nextQuestionSequence: chosenAns.nextQuestionSequence,
|
||||||
answerString = `${answeredQuestion.questionNum}|answer|${chosenAns.answerResult}|${chosenAns.answerText}`;
|
answerResult: chosenAns.answerResult,
|
||||||
}
|
answerText: chosenAns.answerText,
|
||||||
|
});
|
||||||
|
|
||||||
// mark this question as answered (question-chain will read this)
|
// mark this question as answered (question-chain will read this)
|
||||||
glass.questions[answeredQuestion.questionNum - 1].answerSelected =
|
glass.questions[answeredQuestion.questionNum - 1].answerSelected =
|
||||||
|
|
@ -177,6 +186,8 @@ export default {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const self = vm ?? this;
|
const self = vm ?? this;
|
||||||
|
const isIncomplete = !!answer.incomplete;
|
||||||
|
const hadSavedAnswer = !!self.questionsData[answer.index]?.answerData?.answerResult;
|
||||||
|
|
||||||
// clear out any preloaded answers
|
// clear out any preloaded answers
|
||||||
self.selectedAnswers = {};
|
self.selectedAnswers = {};
|
||||||
|
|
@ -193,7 +204,6 @@ export default {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
|
const answeredQuestionText = answeredQuestion.questionText.toUpperCase();
|
||||||
const answeredQuestionAnswer = answeredQuestion.selectedAnswer.toUpperCase();
|
|
||||||
const answeredQuestionAnswerText =
|
const answeredQuestionAnswerText =
|
||||||
answeredQuestion.selectedAnswerText.toUpperCase();
|
answeredQuestion.selectedAnswerText.toUpperCase();
|
||||||
const answeredQuestionNum = answeredQuestion.questionNum;
|
const answeredQuestionNum = answeredQuestion.questionNum;
|
||||||
|
|
@ -208,8 +218,7 @@ export default {
|
||||||
if (answeredQuestionIndex === 0) question.answerSelected = null;
|
if (answeredQuestionIndex === 0) question.answerSelected = null;
|
||||||
|
|
||||||
if (answeredQuestionNum - 1 === questionIndex) {
|
if (answeredQuestionNum - 1 === questionIndex) {
|
||||||
// on the right question
|
question.answerSelected = answeredQuestion.selectedAnswer;
|
||||||
question.answerSelected = answeredQuestionAnswer;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +355,27 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isIncomplete) {
|
||||||
|
// User changed an earlier answer; downstream questions were cleared in the chain
|
||||||
|
self.questionsData[answer.index].answerData = null;
|
||||||
|
self.currentGlassIndex = answer.index;
|
||||||
|
|
||||||
|
if (hadSavedAnswer) {
|
||||||
|
self.questionsData[answer.index].key =
|
||||||
|
(self.questionsData[answer.index].key ??
|
||||||
|
self.questionsData[answer.index].answerKey ??
|
||||||
|
answer.index) + Date.now().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force re-render so the updated selection displays when changing a prior answer
|
||||||
|
self.questionsData[answer.index].key =
|
||||||
|
(self.questionsData[answer.index].key ??
|
||||||
|
self.questionsData[answer.index].answerKey ??
|
||||||
|
answer.index) + Date.now().toString();
|
||||||
|
|
||||||
// set final answer data for the current answered glass part
|
// set final answer data for the current answered glass part
|
||||||
self.questionsData[answer.index].answerData = {
|
self.questionsData[answer.index].answerData = {
|
||||||
answerResult: answer.answerResult,
|
answerResult: answer.answerResult,
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,88 @@ describe("vehicle-questions-mixin", () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.selectedAnswers).toMatchObject({});
|
expect(wrapper.vm.selectedAnswers).toMatchObject({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should clear answerData and keep currentGlassIndex when answer is incomplete", () => {
|
||||||
|
const answer = {
|
||||||
|
incomplete: true,
|
||||||
|
answeredQuestions: [
|
||||||
|
{
|
||||||
|
questionText: "Is this the Overland edition?",
|
||||||
|
selectedAnswer: "1|nextQuestion|5|No",
|
||||||
|
selectedAnswerText: "No",
|
||||||
|
questionNum: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: 0,
|
||||||
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.currentGlassIndex = 1;
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: "Windshield",
|
||||||
|
glassName: "Single",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Is this the Overland edition?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerData: { answerResult: "OLD-PART" },
|
||||||
|
answerKey: "Windshield-Single",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
glassLocation: "Driver",
|
||||||
|
glassName: "Front",
|
||||||
|
questions: [],
|
||||||
|
answerData: { answerResult: "OTHER-PART" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
|
||||||
|
|
||||||
|
expect(wrapper.vm.questionsData[0].answerData).toBeNull();
|
||||||
|
expect(wrapper.vm.questionsData[1].answerData).toBeNull();
|
||||||
|
expect(wrapper.vm.currentGlassIndex).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not bump key when incomplete answer had no saved answer", () => {
|
||||||
|
const answer = {
|
||||||
|
incomplete: true,
|
||||||
|
answeredQuestions: [
|
||||||
|
{
|
||||||
|
questionText: "Is this the Overland edition?",
|
||||||
|
selectedAnswer: "1|nextQuestion|2|Yes",
|
||||||
|
selectedAnswerText: "Yes",
|
||||||
|
questionNum: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: 0,
|
||||||
|
};
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
wrapper.vm.questionsData = [
|
||||||
|
{
|
||||||
|
glassLocation: "Windshield",
|
||||||
|
glassName: "Single",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Is this the Overland edition?",
|
||||||
|
answers: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answerData: null,
|
||||||
|
answerKey: "Windshield-Single",
|
||||||
|
key: "stable-key",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
wrapper.vm.handleCompletedQuestionChainAnswers(answer, "", wrapper.vm);
|
||||||
|
|
||||||
|
expect(wrapper.vm.questionsData[0].key).toBe("stable-key");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("questions in glass parts that are after the answered glass", () => {
|
describe("questions in glass parts that are after the answered glass", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue