Merge branch 'release/2026.08.13' into feature/CASH-2799
This commit is contained in:
commit
1d6c22f54d
3 changed files with 118 additions and 16 deletions
|
|
@ -324,18 +324,65 @@ describe("Question Chain component", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => {
|
test("should defer scroll until after the next question fade enters", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({});
|
const { wrapper } = setupMocks({
|
||||||
|
questionDataProp: [
|
||||||
|
{
|
||||||
|
questionSequence: 1,
|
||||||
|
questionText: "Question 1?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
answerResult: "DB10840",
|
||||||
|
answerText: "No",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionSequence: 3,
|
||||||
|
questionText: "Question 3?",
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
answerResult: "DB09410",
|
||||||
|
answerText: "Yes",
|
||||||
|
nextQuestionSequence: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
const testReturnedAnswer = "1|nextQuestion|3|No";
|
const testReturnedAnswer = "1|nextQuestion|3|No";
|
||||||
|
const scrollSpy = jest.spyOn(wrapper.vm, "scrollCurrentQuestionIntoView");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||||
|
const nextQuestion = wrapper.vm.questions.find((q) => q.questionSequence === 3);
|
||||||
|
|
||||||
await nextTick();
|
//Assert — advancing via nextQuestion no longer scrolls immediately
|
||||||
|
expect(scrollSpy).not.toHaveBeenCalled();
|
||||||
|
expect(wrapper.vm.currentQuestionNum).toBe(3);
|
||||||
|
expect(wrapper.vm.isCurrentQuestion(nextQuestion)).toBe(true);
|
||||||
|
|
||||||
//Assert
|
wrapper.vm.onQuestionEnter(nextQuestion);
|
||||||
expect(Element.prototype.scrollIntoView).toHaveBeenCalled();
|
|
||||||
|
expect(scrollSpy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should not scroll onQuestionEnter for a non-current question", () => {
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
const scrollSpy = jest.spyOn(wrapper.vm, "scrollCurrentQuestionIntoView");
|
||||||
|
const answeredQuestion = wrapper.vm.questions[0];
|
||||||
|
|
||||||
|
wrapper.vm.onQuestionEnter(answeredQuestion);
|
||||||
|
|
||||||
|
expect(scrollSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return answer object if returnedAnswer is a final matching answer", async () => {
|
test("should return answer object if returnedAnswer is a final matching answer", async () => {
|
||||||
|
|
@ -528,6 +575,27 @@ describe("Question Chain component", () => {
|
||||||
expect(wrapper.vm.currentQuestionNum).toBe(2);
|
expect(wrapper.vm.currentQuestionNum).toBe(2);
|
||||||
expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[1])).toBe(true);
|
expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[1])).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("should show only Q1 and Q2 after Cherokee Q1 Yes then Q2 No", async () => {
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
questionDataProp: getCherokeeWindshieldQuestions(),
|
||||||
|
});
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
wrapper.vm.handleAnswer(wrapper.vm.questions[0], "1|nextQuestion|2|Yes");
|
||||||
|
wrapper.vm.handleAnswer(wrapper.vm.questions[1], "2|answer|DW02264|No");
|
||||||
|
|
||||||
|
const visibleQuestions = wrapper.vm.questions.filter((question) =>
|
||||||
|
wrapper.vm.isQuestionVisible(question)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.vm.currentQuestionNum).toBe(0);
|
||||||
|
expect(visibleQuestions).toHaveLength(2);
|
||||||
|
expect(visibleQuestions.map((question) => question.questionSequence)).toEqual([1, 2]);
|
||||||
|
expect(wrapper.vm.isCurrentQuestion(wrapper.vm.questions[0])).toBe(false);
|
||||||
|
expect(wrapper.vm.isCurrentQuestion(wrapper.vm.questions[1])).toBe(false);
|
||||||
|
expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[2])).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-for="(q, i) in questions" :key="i">
|
<div v-for="q in questions" :key="q.questionSequence">
|
||||||
<transition appear name="fade" mode="out-in">
|
<transition
|
||||||
|
appear
|
||||||
|
:css="isCurrentQuestion(q)"
|
||||||
|
:name="isCurrentQuestion(q) ? 'fade' : undefined"
|
||||||
|
mode="out-in"
|
||||||
|
@after-enter="onQuestionEnter(q)">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
v-if="isQuestionVisible(q)"
|
v-if="isQuestionVisible(q)"
|
||||||
|
:key="q.questionSequence"
|
||||||
class="radioQuestion"
|
class="radioQuestion"
|
||||||
:class="isCurrentQuestion(q) && 'current-question'"
|
:class="{ 'current-question': isCurrentQuestion(q) }"
|
||||||
:questionText="q.questionText"
|
:questionText="q.questionText"
|
||||||
:answers="q.answers"
|
:answers="q.answers"
|
||||||
:groupName="`question-${index}-${q.questionSequence}`"
|
:groupName="`question-${index}-${q.questionSequence}`"
|
||||||
|
|
@ -61,7 +67,6 @@ export default {
|
||||||
|
|
||||||
if (this.questions.length > 0) {
|
if (this.questions.length > 0) {
|
||||||
this.initializeCurrentQuestionNum();
|
this.initializeCurrentQuestionNum();
|
||||||
this.scrollCurrentQuestionIntoView();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -105,6 +110,11 @@ export default {
|
||||||
this.currentQuestionNum = this.getQuestionSequence(firstUnanswered);
|
this.currentQuestionNum = this.getQuestionSequence(firstUnanswered);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onQuestionEnter(question) {
|
||||||
|
if (this.isCurrentQuestion(question)) {
|
||||||
|
this.scrollCurrentQuestionIntoView();
|
||||||
|
}
|
||||||
|
},
|
||||||
scrollCurrentQuestionIntoView() {
|
scrollCurrentQuestionIntoView() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
document.querySelector(".current-question")?.scrollIntoView({ behavior: "smooth" });
|
document.querySelector(".current-question")?.scrollIntoView({ behavior: "smooth" });
|
||||||
|
|
@ -201,7 +211,6 @@ export default {
|
||||||
if (questionType === "nextQuestion") {
|
if (questionType === "nextQuestion") {
|
||||||
// update to next question index
|
// update to next question index
|
||||||
this.currentQuestionNum = Number(questionAnswer);
|
this.currentQuestionNum = Number(questionAnswer);
|
||||||
this.scrollCurrentQuestionIntoView();
|
|
||||||
return {
|
return {
|
||||||
incomplete: true,
|
incomplete: true,
|
||||||
answeredQuestions,
|
answeredQuestions,
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@
|
||||||
<div class="container page-container-grouped-styles">
|
<div class="container page-container-grouped-styles">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
||||||
<h5 class="fw-normal mb-0 dark-header" :class="headerColor">
|
<funnelSubHeader
|
||||||
<span>
|
cmsWidgetName="FunnelSubHeaderWidget"
|
||||||
{{ serviceLocationText }}
|
:overrideHeaderSubText="estimatedTimeText"
|
||||||
</span>
|
alignLeft />
|
||||||
</h5>
|
|
||||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" alignLeft />
|
|
||||||
<datePicker
|
<datePicker
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
|
|
@ -104,6 +102,7 @@ import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from "@/constants/sched
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
|
import { getDisplayTextForDurationLength } from "@/helpers/duration-length-helper";
|
||||||
import {
|
import {
|
||||||
flushPagePrereqsLogs,
|
flushPagePrereqsLogs,
|
||||||
hasServiceZipInfo,
|
hasServiceZipInfo,
|
||||||
|
|
@ -309,6 +308,17 @@ export default {
|
||||||
includeMobile: Boolean(mobileProviderNumber),
|
includeMobile: Boolean(mobileProviderNumber),
|
||||||
pageNameToLog: to.name,
|
pageNameToLog: to.name,
|
||||||
});
|
});
|
||||||
|
if (timeSlotsResultMap.inshopTimeSlots) {
|
||||||
|
vm.estimatedServiceMinutesMinimum =
|
||||||
|
timeSlotsResultMap.inshopTimeSlots.estimatedServiceMinutesMinimum;
|
||||||
|
vm.estimatedServiceMinutesMaximum =
|
||||||
|
timeSlotsResultMap.inshopTimeSlots.estimatedServiceMinutesMaximum;
|
||||||
|
} else if (timeSlotsResultMap.mobileTimeSlots) {
|
||||||
|
vm.estimatedServiceMinutesMinimum =
|
||||||
|
timeSlotsResultMap.mobileTimeSlots.estimatedServiceMinutesMinimum;
|
||||||
|
vm.estimatedServiceMinutesMaximum =
|
||||||
|
timeSlotsResultMap.mobileTimeSlots.estimatedServiceMinutesMaximum;
|
||||||
|
}
|
||||||
assignInshopTimeSlotsFromV2Response(
|
assignInshopTimeSlotsFromV2Response(
|
||||||
vm.inshopProvidersAndTimeSlots,
|
vm.inshopProvidersAndTimeSlots,
|
||||||
timeSlotsResultMap.inshopTimeSlots
|
timeSlotsResultMap.inshopTimeSlots
|
||||||
|
|
@ -336,6 +346,19 @@ export default {
|
||||||
viewMoreShopsText() {
|
viewMoreShopsText() {
|
||||||
return this.getCmsContent("ViewMoreShopsWidget", "Text");
|
return this.getCmsContent("ViewMoreShopsWidget", "Text");
|
||||||
},
|
},
|
||||||
|
estimatedTimeText() {
|
||||||
|
if (!this.estimatedServiceMinutesMinimum || !this.estimatedServiceMinutesMaximum) {
|
||||||
|
return " ";
|
||||||
|
}
|
||||||
|
const durationText = getDisplayTextForDurationLength(
|
||||||
|
this.estimatedServiceMinutesMinimum,
|
||||||
|
this.estimatedServiceMinutesMaximum
|
||||||
|
);
|
||||||
|
return this.getCmsContent("FunnelSubHeaderWidget", "HeaderSubText").replace(
|
||||||
|
"{custom:DURATION}",
|
||||||
|
durationText
|
||||||
|
);
|
||||||
|
},
|
||||||
serviceZipCode() {
|
serviceZipCode() {
|
||||||
return store.getters.order.serviceLocation.zipCode;
|
return store.getters.order.serviceLocation.zipCode;
|
||||||
},
|
},
|
||||||
|
|
@ -396,6 +419,8 @@ export default {
|
||||||
datesLoaded: false,
|
datesLoaded: false,
|
||||||
datePickerStartDate: toDateString(0),
|
datePickerStartDate: toDateString(0),
|
||||||
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
|
datePickerEndDate: toDateString(SCHEDULE_FETCH_DAYS - 1),
|
||||||
|
estimatedServiceMinutesMinimum: null,
|
||||||
|
estimatedServiceMinutesMaximum: null,
|
||||||
inshopProvidersAndTimeSlots: [],
|
inshopProvidersAndTimeSlots: [],
|
||||||
allShopProviders: [],
|
allShopProviders: [],
|
||||||
mobileProviderAndTimeSlot: null,
|
mobileProviderAndTimeSlot: null,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue