571 lines
22 KiB
JavaScript
571 lines
22 KiB
JavaScript
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 <Form>, which vee-validate intercepts and
|
|
// fully (re)validates every field using that field's own internally tracked value -
|
|
// not necessarily whatever is visible in the DOM input. If that internal value is
|
|
// stale (the underlying bug), the message can still fail to appear even though
|
|
// meta.valid correctly flips to false while typing.
|
|
test("shows the CMS validation message in the DOM after typing an invalid value and submitting the form", 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 harness = {
|
|
components: { Form, textboxQuestion },
|
|
data() {
|
|
return { policyNumber: "ABCDEFGH" };
|
|
},
|
|
template: `
|
|
<Form v-slot="{ meta }" @submit="() => {}">
|
|
<textboxQuestion
|
|
v-model="policyNumber"
|
|
inputId="policyNumber"
|
|
validationRules="policy-number-required|policy-number-format" />
|
|
<button type="submit" :disabled="!meta.valid">Continue</button>
|
|
</Form>
|
|
`,
|
|
};
|
|
|
|
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 <form>.
|
|
await wrapper.find("form").trigger("submit");
|
|
await flushPromises();
|
|
|
|
expect(wrapper.text()).toContain(minValidationMessage);
|
|
});
|
|
});
|
|
});
|
|
|
|
function setupMocks() {
|
|
const mockCmsContent = {
|
|
FunnelHeaderWidget: { Text: "Header" },
|
|
FunnelSubHeaderWidget: { Text: "Subheader" },
|
|
FunnelFooterWidget: { Text: "Footer" },
|
|
};
|
|
|
|
fetchCmsContentForPage.mockResolvedValue(mockCmsContent);
|
|
settleAllPromises.mockResolvedValue({ cmsContent: mockCmsContent });
|
|
|
|
const mockMixin = {
|
|
methods: {
|
|
getCmsContent: jest.fn((widgetName, fieldName) => {
|
|
return mockCmsContent?.[widgetName]?.[fieldName] ?? "";
|
|
}),
|
|
setCmsContent: jest.fn(),
|
|
},
|
|
};
|
|
|
|
const mountOptions = getMountOptions({
|
|
route: { name: "policy-info", query: {}, params: {} },
|
|
router: {
|
|
navigate: jest.fn(),
|
|
navigateWithSaving: jest.fn(),
|
|
navigateWithoutSaving: jest.fn(),
|
|
},
|
|
mixins: [mockMixin],
|
|
});
|
|
|
|
const wrapper = shallowMount(policyInfo, mountOptions);
|
|
|
|
return { wrapper };
|
|
}
|
|
|
|
// Runs beforeRouteEnter with a clientConfig that includes the given
|
|
// PolicyNumberValidationWidget config so the module-scoped rule config gets populated,
|
|
// mirroring how the real CMS response would flow through the route guard. Like
|
|
// ConfigWidget, the real CMS wraps the widget's config in a JSON-encoded `Text`
|
|
// string rather than returning a plain object, so we replicate that shape here too.
|
|
async function setupMocksWithPolicyNumberValidationWidget(policyNumberValidationConfig) {
|
|
const { wrapper } = setupMocks();
|
|
|
|
settleAllPromises.mockResolvedValue({
|
|
cmsContent: {},
|
|
clientConfig: {
|
|
PolicyNumberValidationWidget: policyNumberValidationConfig
|
|
? { Text: JSON.stringify(policyNumberValidationConfig) }
|
|
: undefined,
|
|
},
|
|
});
|
|
|
|
await policyInfo.beforeRouteEnter.call(
|
|
wrapper.vm,
|
|
{ name: "policy-info" },
|
|
undefined,
|
|
(callback) => callback(wrapper.vm)
|
|
);
|
|
|
|
return { wrapper };
|
|
}
|