From 275a30ee4d0d358c1ea9d105e95194d7547573ee Mon Sep 17 00:00:00 2001 From: Philipp Reinking Date: Sat, 17 Oct 2026 12:47:49 +0200 Subject: [PATCH 1/4] Say when an answer is too long to store Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 1 + .../js/forms/classic/layout/Block.test.ts | 58 +++++++++++++++++++ resources/js/forms/classic/layout/Block.vue | 2 +- resources/js/stores/conversation.ts | 16 ++++- resources/locales/de.json | 1 + resources/locales/en.json | 1 + resources/locales/fr.json | 1 + resources/locales/no.json | 1 + resources/locales/pl.json | 1 + resources/locales/sk.json | 1 + resources/locales/uk.json | 1 + resources/locales/zh.json | 1 + 12 files changed, 83 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 448a15af..cd93c6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Lines marked **Upgrade step** need you to act after updating. - While your forms load, the form list shows placeholder rows. When the list can't load, it says so and offers "Try again". A search or status without forms says so and offers a way back, and screen readers hear how many forms a search finds. Before, all of these said you hadn't created a survey yet. A team without any forms now sees "Create your first form". A form you publish, unpublish, archive or restore in the list moves to its tab right away, and the tab counts follow. (#249) - A form template with a description, language, color, button label, completion headline or link that is too long is now refused with a message that names the field, and the form stays as it was. Before, the import failed with a server error on MySQL and MariaDB. (#251) - With a dialog open, Tab and Shift+Tab now stay inside the dialog, and closing it puts you back on the button that opened it. Before, Tab moved on to the page behind the dialog. (#252) +- When a visitor's answer is too long to store, the form now says so and asks them to shorten it, in the form's language. Before, it only said "Your answers could not be sent". (#PR) - The form list's dates and blue dots now update while the page stays open, every minute and when you come back to the tab. Before, "12 min ago" stayed until you reloaded the page. The status tab counts and the search count now write numbers like the rest of the list, also in a browser set to another language. (#254) - A submission where every question was skipped now counts. Before, a form with only such submissions showed "0 submissions" next to a recent submission date in the form list, and "No submissions found" on its submissions page. Forms with such submissions now show a higher count and completion rate. (#253) - A form settings page now saves only its own settings. Before, a bad value saved on another settings page could block the save, with an error about a field that isn't on the page. When a save is refused, the editor keeps what was last saved, so the refused change no longer shows on the other settings pages. A good image upload after a failed one no longer shows the old error. (#255) diff --git a/resources/js/forms/classic/layout/Block.test.ts b/resources/js/forms/classic/layout/Block.test.ts index 893e49af..4ed09b4b 100644 --- a/resources/js/forms/classic/layout/Block.test.ts +++ b/resources/js/forms/classic/layout/Block.test.ts @@ -45,3 +45,61 @@ describe("Block", () => { ).toBe(false); }); }); + +describe("Block submit errors", () => { + const submitWith = async (error: unknown) => { + const pinia = createPinia(); + setActivePinia(pinia); + + const block = { + id: "block1", + type: "none", + interactions: [], + } as unknown as PublicFormBlockModel; + + const store = useConversation(); + store.form = { uuid: "form" } as PublicFormModel; + store.session = { token: "session" } as FormSessionModel; + store.queue = [block]; + store.current = "block1"; + + vi.spyOn(console, "warn").mockImplementation(() => {}); + vi.mocked(callSubmitForm).mockRejectedValueOnce(error); + + const wrapper = mount(Block, { + props: { block }, + global: { plugins: [pinia], provide: { disableFocus: true } }, + }); + + await wrapper.find("form").trigger("submit"); + await flushPromises(); + + return wrapper.text(); + }; + + it("says the answer is too long when the server refuses it as too long", async () => { + const text = await submitWith({ + response: { + status: 422, + data: { errors: { "payload.block1": ["Too long."] } }, + }, + }); + + expect(text).toContain(localeDE.submit_too_long); + expect(text).not.toContain(localeDE.submit_failed); + }); + + it.each([ + ["a 500", { response: { status: 500, data: {} } }], + ["a lost connection", new Error("Network Error")], + [ + "another 422", + { response: { status: 422, data: { errors: { token: ["x"] } } } }, + ], + ])("still shows the retry message for %s", async (_, error) => { + const text = await submitWith(error); + + expect(text).toContain(localeDE.submit_failed); + expect(text).not.toContain(localeDE.submit_too_long); + }); +}); diff --git a/resources/js/forms/classic/layout/Block.vue b/resources/js/forms/classic/layout/Block.vue index 02fe28e3..96fe567c 100644 --- a/resources/js/forms/classic/layout/Block.vue +++ b/resources/js/forms/classic/layout/Block.vue @@ -25,7 +25,7 @@
- {{ t("submit_failed") }} + {{ t(store.submitTooLong ? "submit_too_long" : "submit_failed") }}