From fa0e80ca404697d8e6f7db357d904710503e14e1 Mon Sep 17 00:00:00 2001 From: Davide Di Pumpo Date: Thu, 10 Sep 2026 20:03:25 +0200 Subject: [PATCH] fix(vue-components): handle string form validation errors --- .changeset/tidy-forms-report.md | 5 ++ .../__tests__/OmegaForm/GeneralErrors.test.ts | 59 +++++++++++++++++++ .../OmegaForm/OmegaErrorsInternal.vue | 35 ++++++----- 3 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 .changeset/tidy-forms-report.md create mode 100644 packages/vue-components/__tests__/OmegaForm/GeneralErrors.test.ts diff --git a/.changeset/tidy-forms-report.md b/.changeset/tidy-forms-report.md new file mode 100644 index 0000000000..b4d1a58d75 --- /dev/null +++ b/.changeset/tidy-forms-report.md @@ -0,0 +1,5 @@ +--- +"@effect-app/vue-components": patch +--- + +Handle string form validation errors in OmegaForm.Errors without crashing, and safely ignore unsupported error values. diff --git a/packages/vue-components/__tests__/OmegaForm/GeneralErrors.test.ts b/packages/vue-components/__tests__/OmegaForm/GeneralErrors.test.ts new file mode 100644 index 0000000000..33a03f7ea7 --- /dev/null +++ b/packages/vue-components/__tests__/OmegaForm/GeneralErrors.test.ts @@ -0,0 +1,59 @@ +import { mount } from "@vue/test-utils" +import * as S from "effect-app/Schema" +import { describe, expect, it } from "vitest" +import { defineComponent, h, nextTick } from "vue" +import { useOmegaForm } from "../../src/components/OmegaForm" +import OmegaErrorsInternal from "../../src/components/OmegaForm/OmegaErrorsInternal.vue" + +describe("OmegaForm general errors", () => { + it("renders a string returned by an onSubmit validator and clears it after correction", async () => { + const message = "Passwörter stimmen nicht überein" + const Inner = defineComponent({ + setup() { + const form = useOmegaForm(S.Struct({ password: S.String, confirmation: S.String }), { + defaultValues: { password: "secret", confirmation: "different" }, + validators: { + onSubmit: ({ value }) => value.password === value.confirmation ? undefined : message + } + }) + return { form } + }, + template: `` + }) + const wrapper = mount(Inner) + + await wrapper.vm.form.handleSubmit() + await nextTick() + expect(wrapper.get("[role=\"alert\"]").text()).toContain(message) + + wrapper.vm.form.setFieldValue("confirmation", "secret") + await wrapper.vm.form.handleSubmit() + await nextTick() + expect(wrapper.find("[role=\"alert\"]").exists()).toBe(false) + wrapper.unmount() + }) + + it("preserves schema messages alongside strings and ignores unsupported values", () => { + const wrapper = mount(OmegaErrorsInternal, { + props: { + errors: [], + generalErrors: [ + "Form error", + { rows: [{ message: "Schema error" }, null, 42, { message: 123 }, { message: "" }] }, + null, + undefined, + false, + 42, + "", + { rows: "unsupported", other: null } + ] + }, + slots: { + default: ({ showedGeneralErrors }) => h("div", JSON.stringify(showedGeneralErrors)) + } + }) + + expect(wrapper.text()).toBe(JSON.stringify(["Form error", "Schema error"])) + wrapper.unmount() + }) +}) diff --git a/packages/vue-components/src/components/OmegaForm/OmegaErrorsInternal.vue b/packages/vue-components/src/components/OmegaForm/OmegaErrorsInternal.vue index ca07d72383..1e5a848e05 100644 --- a/packages/vue-components/src/components/OmegaForm/OmegaErrorsInternal.vue +++ b/packages/vue-components/src/components/OmegaForm/OmegaErrorsInternal.vue @@ -99,7 +99,6 @@