import policyInfo from "@/layouts/policy-info/policy-info.vue"; import { shallowMount, mount, flushPromises } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { settleAllPromises } from "@/helpers/layout-helper"; import { validate, Form } from "vee-validate"; import { errorMessages } from "@/constants/error-messages"; import textboxQuestion from "@/digital-components/textbox-question/textbox-question"; // Mock our module for promises. jest.mock("@/helpers/layout-helper.js", () => ({ settleAllPromises: jest.fn(), })); // Mock fetchCmsContentForPage jest.mock("@/helpers/cms-content-helper", () => ({ fetchCmsContentForPage: jest.fn(), })); describe("policy-info.vue", () => { describe("component rendering", () => { test("renders funnelHeader component", () => { const { wrapper } = setupMocks(); expect(wrapper.findComponent({ name: "funnelHeader" }).exists()).toBe(true); }); test("renders funnelSubHeader component", () => { const { wrapper } = setupMocks(); expect(wrapper.findComponent({ name: "funnelSubHeader" }).exists()).toBe(true); }); test("renders navbar component", () => { const { wrapper } = setupMocks(); expect(wrapper.findComponent({ name: "insuranceNavBar" }).exists()).toBe(true); }); test("renders Form component", () => { const { wrapper } = setupMocks(); expect(wrapper.findComponent({ name: "Form" }).exists()).toBe(true); }); }); describe("methods", () => { test("arePagePrerequisitesValid returns true", () => { const { wrapper } = setupMocks(); expect(wrapper.vm.arePagePrerequisitesValid()).toBe(true); }); }); describe("displayClaimNumber", () => { test("is false when no morePolicyQuestions flags are set", async () => { const { wrapper } = setupMocks(); wrapper.vm.morePolicyQuestions = { additionalDamage: false, rental: false, otherResponsibleParty: false, thirdPartyVehicle: false, injuries: false, }; expect(wrapper.vm.displayClaimNumber).toBe(false); }); test("is true when additionalDamage is checked", async () => { const { wrapper } = setupMocks(); wrapper.vm.morePolicyQuestions = { additionalDamage: true, rental: false, otherResponsibleParty: false, thirdPartyVehicle: false, injuries: false, }; expect(wrapper.vm.displayClaimNumber).toBe(true); }); test("is true when rental is checked", async () => { const { wrapper } = setupMocks(); wrapper.vm.morePolicyQuestions = { additionalDamage: false, rental: true, otherResponsibleParty: false, thirdPartyVehicle: false, injuries: false, }; expect(wrapper.vm.displayClaimNumber).toBe(true); }); test("is false when only unrelated flags (e.g. thirdPartyVehicle, injuries) are checked", async () => { const { wrapper } = setupMocks(); wrapper.vm.morePolicyQuestions = { additionalDamage: false, rental: false, otherResponsibleParty: false, thirdPartyVehicle: true, injuries: true, }; expect(wrapper.vm.displayClaimNumber).toBe(false); }); test("is false when morePolicyQuestions is not yet populated", async () => { const { wrapper } = setupMocks(); wrapper.vm.morePolicyQuestions = null; expect(wrapper.vm.displayClaimNumber).toBe(false); }); }); describe("claim-number-required validation rule", () => { test("fails when value is empty", async () => { const result = await validate("", "claim-number-required"); expect(result.valid).toBe(false); expect(result.errors).toContain(errorMessages.CLAIM_NUMBER_REQUIRED); }); test("passes when value is provided", async () => { const result = await validate("12345678", "claim-number-required"); expect(result.valid).toBe(true); }); }); describe("forwardButtonAction", () => { // Regression coverage: clicking Continue must explicitly (re)validate the form // rather than assuming meta.valid/isForwardActionDisabled already caught everything, // since that was the actual gap behind "I typed an invalid policy number, clicked // Continue, and the message never appeared". test("does not save or navigate when the form is invalid", async () => { const { wrapper } = setupMocks(); wrapper.vm.isFormValid = jest.fn().mockResolvedValue(false); await wrapper.vm.forwardButtonAction(); expect(wrapper.vm.isFormValid).toHaveBeenCalledWith(wrapper.vm.$refs.theForm); expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithSaving).not.toHaveBeenCalled(); }); test("saves and navigates when the form is valid", async () => { const { wrapper } = setupMocks(); wrapper.vm.isFormValid = jest.fn().mockResolvedValue(true); await wrapper.vm.forwardButtonAction(); expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled(); expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled(); }); }); describe("policy-number-format validation rule", () => { test("passes when no PolicyNumberValidationWidget config is returned", async () => { const { wrapper } = await setupMocksWithPolicyNumberValidationWidget(undefined); const result = await validate("AB1", "policy-number-format"); expect(result.valid).toBe(true); expect(wrapper.vm).toBeTruthy(); }); test("passes when field is empty (required rule handles that case)", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("", "policy-number-format"); expect(result.valid).toBe(true); }); test("fails when value is shorter than minimumLength", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEF", "policy-number-format"); expect(result.valid).toBe(false); }); test("fails when value is longer than maximumLength", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEFGHIJ", "policy-number-format"); expect(result.valid).toBe(false); }); test("passes for a valid Alpha value within length bounds", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEFGH", "policy-number-format"); expect(result.valid).toBe(true); }); test("fails for an Alpha rule when value contains digits", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEFG1", "policy-number-format"); expect(result.valid).toBe(false); }); test("passes for a valid numeric value", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 6, maximumLength: 10, validationRuleType: "numeric", }); const result = await validate("123456", "policy-number-format"); expect(result.valid).toBe(true); }); test("fails for a numeric rule when value contains letters", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 6, maximumLength: 10, validationRuleType: "numeric", }); const result = await validate("12345A", "policy-number-format"); expect(result.valid).toBe(false); }); test("passes for a valid AlphaNumeric value", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 9, maximumLength: 12, validationRuleType: "AlphaNumeric", }); const result = await validate("123456789a", "policy-number-format"); expect(result.valid).toBe(true); }); test("fails for an AlphaNumeric rule when value contains a space", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 9, maximumLength: 12, validationRuleType: "AlphaNumeric", }); const result = await validate("12345678 a", "policy-number-format"); expect(result.valid).toBe(false); }); test("fails for an AlphaNumeric rule when value contains a dash", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 9, maximumLength: 12, validationRuleType: "AlphaNumeric", }); const result = await validate("12345678-a", "policy-number-format"); expect(result.valid).toBe(false); }); test("passes for a valid AlphaNumericWithSpaces value", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 6, maximumLength: 12, validationRuleType: "AlphaNumericWithSpaces", }); const result = await validate("ABC 123", "policy-number-format"); expect(result.valid).toBe(true); }); test("fails for an AlphaNumericWithSpaces rule when value contains other symbols", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 6, maximumLength: 12, validationRuleType: "AlphaNumericWithSpaces", }); const result = await validate("ABC-123", "policy-number-format"); expect(result.valid).toBe(false); }); test("uses the CMS-provided minValidationMessage when value is shorter than minimumLength", async () => { const minValidationMessage = "Policy number should be at least 8 characters."; await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", minValidationMessage, maxValidationMessage: "Policy number must be 9 characters or less.", }); const result = await validate("ABCDEF", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([minValidationMessage]); }); test("uses the CMS-provided maxValidationMessage when value is longer than maximumLength", async () => { const maxValidationMessage = "Policy number must be 9 characters or less."; await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", minValidationMessage: "Policy number should be at least 8 characters.", maxValidationMessage, }); const result = await validate("ABCDEFGHIJ", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([maxValidationMessage]); }); test("falls back to the default error message when minValidationMessage is not provided", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEF", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([errorMessages.POLICY_NUMBER_FORMAT]); }); test("falls back to the default error message when maxValidationMessage is not provided", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", }); const result = await validate("ABCDEFGHIJ", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([errorMessages.POLICY_NUMBER_FORMAT]); }); test("uses the CMS-provided invalidCharMessage for a pattern-only failure (value within length bounds)", async () => { const invalidCharMessage = "Policy number may only contain letters."; await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", minValidationMessage: "Policy number should be at least 8 characters.", maxValidationMessage: "Policy number must be 9 characters or less.", invalidCharMessage, }); const result = await validate("ABCDEFG1", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([invalidCharMessage]); }); test("falls back to the default error message for a pattern-only failure when invalidCharMessage is not provided", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", minValidationMessage: "Policy number should be at least 8 characters.", maxValidationMessage: "Policy number must be 9 characters or less.", }); const result = await validate("ABCDEFG1", "policy-number-format"); expect(result.valid).toBe(false); expect(result.errors).toEqual([errorMessages.POLICY_NUMBER_FORMAT]); }); }); describe("policyNumber field end-to-end (via textbox-question)", () => { // Regression test for a reported bug: typing a value that never passes through a // "valid" intermediate state (e.g. "a@2" against an 8-9 char Alpha-only rule) left // vee-validate's field `meta.valid` stuck at `true`, since textbox-question's value // watcher only synced/validated the field when a pre-check said the value was valid. // In the app this drives the Forward button's disabled state // (:isForwardActionDisabled="!meta.valid"), so the bug meant a user could type an // invalid policy number and the form would not flag it as invalid at all - it only // "self-corrected" via the input's native `change` event on blur, which doesn't fire // from keystrokes alone. This test dispatches only "input" events (like real typing) // and intentionally does NOT fire a native "change"/blur event, to isolate the // watcher's own behavior from that unrelated safety net. test("marks the field invalid as soon as an unrecoverable value is typed, without waiting for blur", async () => { await setupMocksWithPolicyNumberValidationWidget({ minimumLength: 8, maximumLength: 9, validationRuleType: "Alpha", minValidationMessage: "Policy number should be at least 8 characters.", maxValidationMessage: "Policy number must be 9 characters or less.", }); const wrapper = mount(textboxQuestion, { global: { directives: { maska: jest.fn() }, mixins: [ { methods: { getCmsContent: jest.fn(() => ""), }, }, ], }, props: { // Start from a value that already satisfies the rule, so meta.valid // begins as true (isolating the watcher's behavior from the // required-on-empty check, which is independently already true on // mount regardless of this bug). modelValue: "ABCDEFGH", inputId: "policyNumber", validationRules: "policy-number-required|policy-number-format", "onUpdate:modelValue": (newValue) => wrapper.setProps({ modelValue: newValue }), }, }); await flushPromises(); expect(wrapper.vm.meta.valid).toBe(true); const input = wrapper.find("input"); input.element.value = "a@2"; await input.trigger("input"); // simulate typing only, no blur/change await flushPromises(); expect(wrapper.vm.meta.valid).toBe(false); }); // Reproduces "I typed a@2 and clicked Continue, and the message never appeared". // Clicking Continue submits the real
`, }; const wrapper = mount(harness, { global: { directives: { maska: jest.fn() }, mixins: [ { methods: { getCmsContent: jest.fn(() => ""), }, }, ], }, }); await flushPromises(); const input = wrapper.find("input"); input.element.value = "a@2"; await input.trigger("input"); // simulate typing only, no blur/change await flushPromises(); // Mirrors clicking the real "Continue" button, which submits the