import { shallowMount } from "@vue/test-utils"; import questionChain from "./question-chain.vue"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { nextTick } from "vue"; jest.mock( "@/store", () => { return {}; }, { virtual: true } ); describe("Question Chain component", () => { describe("on create...", () => { test("Should populate questions data array", async () => { //Arrange const { wrapper } = setupMocks({}); const expectedFirstQuestionAnswer = { answerResult: "DB09410", buttonLabel: "Yes", nextQuestionSequence: null, questionSequence: 1, questionType: "answer", value: "1|answer|DB09410|Yes", }; //Act await nextTick(); //Assert expect(wrapper.vm.questions[0].answers[0]).toMatchObject(expectedFirstQuestionAnswer); }); test("Should not include suppressed questions on questions data array", async () => { //Arrange const { wrapper } = setupMocks({}); await wrapper.setData({ questionData: [ { questionSequence: 1, questionText: "Question here?", answers: [ { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, }, ], suppressThisQuestion: true, }, ], }); //Act await nextTick(); //Assert expect(wrapper.vm.questions[0]).toBeFalsy(); }); }); describe("method handleAnswer...", () => { test("should run getQuestionChainAnswerIfComplete", () => { //Arrange const { wrapper } = setupMocks({}); const testQuestion = {}; const testReturnedAnswer = "aReturnedAnswer"; wrapper.vm.getQuestionChainAnswerIfComplete = jest.fn(); //Act wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer); //Assert expect(wrapper.vm.getQuestionChainAnswerIfComplete).toBeCalled(); }); test("should emit update:modelValue if returnedAnswer contains 'answer'", () => { //Arrange const { wrapper } = setupMocks({}); const testQuestion = {}; const testReturnedAnswer = "1|answer|DB10840|No"; //Act wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer); //Assert expect(wrapper.emitted()).toHaveProperty("update:modelValue"); expect(wrapper.emitted()["update:modelValue"][0][0]).toMatchObject({ answerResult: "DB10840", }); }); test("should NOT emit update:modelValue if no returnedAnswer", () => { //Arrange const { wrapper } = setupMocks({}); const testQuestion = {}; //Act wrapper.vm.handleAnswer(testQuestion); //Assert expect(wrapper.emitted()).not.toHaveProperty("update:modelValue"); }); test("should not emit update:modelValue for nextQuestion during normal forward progress", () => { //Arrange const { wrapper } = setupMocks({}); const testQuestion = {}; const testReturnedAnswer = "1|nextQuestion|3|No"; //Act wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer); //Assert 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...", () => { test("should return false if no returned answer", () => { //Arrange const { wrapper } = setupMocks({}); //Act const result = wrapper.vm.getQuestionChainAnswerIfComplete(); //Assert expect(result).toBeFalsy(); }); test("should set the answerSelected to match the answered one", async () => { //Arrange const { wrapper } = setupMocks({}); const testReturnedAnswer = "1|answer|DB10840|No"; await wrapper.setData({ questionData: [ { questionSequence: 1, questionText: "Question here?", answers: [ { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, }, ], }, ], }); //Act wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); //Assert expect(wrapper.vm.questions[0].answerSelected).toBe("1|answer|DB10840|No"); }); test("should remove answers of questions after the answered one", async () => { //Arrange const { wrapper } = setupMocks({}); const testReturnedAnswer = "1|answer|DB10840|No"; await wrapper.setData({ questionData: [ { questionSequence: 1, questionText: "Question here?", answers: [ { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, }, ], }, { questionSequence: 2, questionText: "Question 2", answers: [ { answerResult: "2-yes", answerText: "Yes", nextQuestionSequence: null, }, { answerResult: "2-no", answerText: "No", nextQuestionSequence: null, }, ], answerSelected: "previously selected answer", }, ], }); //Act wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); //Assert expect(wrapper.vm.questions[1].answerSelected).toBeUndefined(); }); test("should 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 const { wrapper } = setupMocks({}); const testReturnedAnswer = "1|nextQuestion|3|No"; //Act const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); //Assert expect(result).toMatchObject({ incomplete: true, index: 0, }); }); test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => { //Arrange const { wrapper } = setupMocks({}); const testReturnedAnswer = "1|nextQuestion|3|No"; //Act wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); await nextTick(); //Assert expect(Element.prototype.scrollIntoView).toHaveBeenCalled(); }); test("should return answer object if returnedAnswer is a final matching answer", async () => { //Arrange const { wrapper } = setupMocks({ questionDataProp: [ { questionSequence: 1, questionText: "Question here?", answers: [ { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, problemQuestionId: 9529, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, problemQuestionId: 9531, }, ], }, ], }); const testReturnedAnswer = "1|answer|DB10840|No"; await wrapper.setProps({ index: 0 }); await nextTick(); //Act const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer); //Assert expect(result).toMatchObject({ answerResult: "DB10840", problemQuestionId: 9531, index: 0, }); expect(result.answeredQuestions).toEqual([ { questionText: "Question here?", selectedAnswer: "1|answer|DB10840|No", selectedAnswerText: "No", questionNum: 1, problemQuestionId: 9531, }, ]); }); test("should include problemQuestionId from parts-or-questions answer on created", async () => { //Arrange const { wrapper } = setupMocks({ questionDataProp: [ { questionSequence: 1, questionText: "Does the rubber seal around your windshield have a chrome strip running through it?", answers: [ { answerResult: "WCR 848", answerText: "Yes", nextQuestionSequence: null, problemQuestionId: 9531, }, { answerResult: "WCR 848", answerText: "No", nextQuestionSequence: null, problemQuestionId: 9529, }, ], }, ], }); //Act await nextTick(); //Assert expect(wrapper.vm.questions[0].answers[0].problemQuestionId).toBe(9531); 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); }); test("should resolve on Q1 Yes and not show Q2 for Honda HUD question", async () => { const { wrapper } = setupMocks({ questionDataProp: getHondaAccordWindshieldQuestions(), }); await nextTick(); const q1 = wrapper.vm.questions[0]; wrapper.vm.handleAnswer(q1, "1|answer|FW04796|Yes"); expect(wrapper.emitted()["update:modelValue"][0][0]).toMatchObject({ answerResult: "FW04796", index: 0, }); expect(wrapper.vm.currentQuestionNum).toBe(0); expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[1])).toBe(false); }); test("should resolve on Q3 No and not continue after remount", async () => { const questionDataProp = getHondaAccordWindshieldQuestions(); questionDataProp[0].answerSelected = "1|nextQuestion|2|No"; questionDataProp[1].answerSelected = "2|nextQuestion|3|No"; questionDataProp[2].answerSelected = "3|answer|FW04793|No"; const { wrapper } = setupMocks({ questionDataProp }); await nextTick(); expect(wrapper.vm.currentQuestionNum).toBe(0); expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[2])).toBe(true); expect( wrapper.vm.questions.filter((question) => wrapper.vm.isQuestionVisible(question)) ).toHaveLength(3); }); test("should show Q2 after answering No on Honda HUD question", async () => { const { wrapper } = setupMocks({ questionDataProp: getHondaAccordWindshieldQuestions(), }); await nextTick(); const q1 = wrapper.vm.questions[0]; wrapper.vm.handleAnswer(q1, "1|nextQuestion|2|No"); expect(wrapper.vm.currentQuestionNum).toBe(2); expect(wrapper.vm.isQuestionVisible(wrapper.vm.questions[1])).toBe(true); }); }); }); function getHondaAccordWindshieldQuestions() { return [ { questionSequence: 1, questionText: "Is your vehicle equipped with a Heads-up Display which projects vehicle information, such as speed, onto the windshield?", answers: [ { answerResult: "FW04796", answerText: "Yes", nextQuestionSequence: null }, { answerResult: "", answerText: "No", nextQuestionSequence: 2 }, ], }, { questionSequence: 2, questionText: "Is your vehicle equipped with an auto-dimming rearview mirror which will darken automatically when a vehicles approaches from the rear at night?", answers: [ { answerResult: "FW04795", answerText: "Yes", nextQuestionSequence: null }, { answerResult: "", answerText: "No", nextQuestionSequence: 3 }, ], }, { questionSequence: 3, questionText: "Is your vehicle equipped with a power moonroof?", answers: [ { answerResult: "FW04794", answerText: "Yes", nextQuestionSequence: null }, { answerResult: "FW04793", answerText: "No", nextQuestionSequence: null }, ], }, ]; } 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({ questionDataProp = [ { questionSequence: 1, questionText: "Does only your center sliding piece need to be replaced?", answers: [ { answerResult: "DB09410", answerText: "Yes", nextQuestionSequence: null, }, { answerResult: "DB10840", answerText: "No", nextQuestionSequence: null, }, ], }, ], }) { Element.prototype.scrollIntoView = jest.fn(); const mountOptions = getMountOptions({}); mountOptions.propsData = { questionData: questionDataProp, index: 0, }; mountOptions["attachTo"] = document.body; const wrapper = shallowMount(questionChain, mountOptions); return { wrapper }; }