diff --git a/src/common-components/question-chain/question-chain.spec.js b/src/common-components/question-chain/question-chain.spec.js
new file mode 100644
index 00000000..64260ddf
--- /dev/null
+++ b/src/common-components/question-chain/question-chain.spec.js
@@ -0,0 +1,314 @@
+import { shallowMount } from "@vue/test-utils";
+import questionChain from "@/common-components/question-chain/question-chain.vue";
+import { getMountOptions } from "@/helpers/unit-test-helper.js";
+import { nextTick } from "vue";
+
+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 if returnedAnswer contains 'nextQuestion'", () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ const testQuestion = {};
+ const testReturnedAnswer = "1|nextQuestion|DB10840|No";
+
+ //Act
+ wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
+
+ //Assert
+ expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
+ });
+ });
+
+ 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 return false if returnedAnswer is a nextQuestion (not a final answer)", async () => {
+ //Arrange
+ const { wrapper } = setupMocks({});
+ const testReturnedAnswer = "1|nextQuestion|DB10840|No";
+
+ //Act
+ const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
+
+ //Assert
+ expect(result).toBeFalsy();
+ });
+
+ 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|DB10840|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({});
+ const testReturnedAnswer = "1|answer|DB10840|No";
+ await wrapper.setData({
+ questions: [
+ {
+ questionSequence: 1,
+ questionText: "Question here?",
+ answers: [
+ {
+ answerResult: "DB09410",
+ answerText: "Yes",
+ nextQuestionSequence: null,
+ },
+ {
+ answerResult: "DB10840",
+ answerText: "No",
+ nextQuestionSequence: null,
+ },
+ ],
+ },
+ ],
+ index: 0,
+ });
+
+ //Act
+ const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
+
+ //Assert
+ expect(result).toMatchObject({
+ answerResult: "DB10840",
+ answeredQuestions: [
+ { questionNum: 1, questionText: "Question here?", selectedAnswerText: "No" },
+ {
+ questionNum: 1,
+ questionText: "Does only your center sliding piece need to be replaced?",
+ selectedAnswerText: "No",
+ },
+ ],
+ index: 0,
+ });
+ });
+ });
+});
+
+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,
+ };
+ mountOptions["attachTo"] = document.body;
+
+ const wrapper = shallowMount(questionChain, mountOptions);
+
+ return { wrapper };
+}
diff --git a/src/common-components/question-chain/question-chain.vue b/src/common-components/question-chain/question-chain.vue
new file mode 100644
index 00000000..11874670
--- /dev/null
+++ b/src/common-components/question-chain/question-chain.vue
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+