diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5af102..800dd2ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ Lines marked **Upgrade step** need you to act after updating. - The form's team can try a form in preview mode, also while it's a draft. Answers in preview mode are never stored or counted and send no webhooks or emails. (#266) - Add `?preview=1` to your form's link to try it as a member of its team. Nothing is stored, files aren't uploaded and the form doesn't redirect at the end. (#270) - Add a question right where you need it: between questions or from a question's menu, and type "/" in an empty question to pick its answer type. A picker groups the answer types as Text, Choice, Rating, Other and Content, and the new question opens ready to type. The answer types have new names, like Single choice, Short text and Text only. (#276) -- The form editor shows the end screen as the last block of your form. You can edit its headline, text and button right there, and the Completion Page in Settings shows the same values. (#271) +- The form editor shows the end screen as the last block of your form. You can edit its headline, text and button right there, and the End screen tab in Settings shows the same values. (#271) - "Preview" in the editor shows your form beside the questions, on tablet and phone as a panel. It follows the question you edit, and the editor follows when you click Next in it. On the other form pages, "Preview" opens your form in a new tab in preview mode. Answers in either preview aren't saved. (#273) ### Changed @@ -30,6 +30,7 @@ Lines marked **Upgrade step** need you to act after updating. - Deleting a question, a group, an answer choice, a consent policy or a logic rule in the form editor now asks first in the app's own dialog. It names the question, how many answers will be deleted with it, and the questions whose rules will stop working. A choice nobody picked, or a policy nobody agreed to, is still deleted at once. (#269) - Each rule now shows as a sentence under its question, and a group says when it's hidden. Edit changes a rule right there, and for choices, ratings and scales you pick the answer instead of typing it. A rule whose question was deleted shows in red and can't be saved until you fix it. (#274) - The form editor uses plain words: a question's settings are headed by its answer type, like Choices or Rating, "Identifier" is now "Short name", and every switch shows on or off. Settings name what they do, like "Lowest value", "Shuffle choices" and "None ticked: all types allowed." on file uploads. (#279) +- The editor's preview shows the end screen while you edit it, with your latest text, and says so when your form sends people to a link instead. Its button opens in a new tab there, so you stay in the editor, and typing in a question you just opened no longer goes into the preview. On a tablet or phone, Esc closes the preview even after you clicked into it. Settings now calls the Completion Page "End screen", like the editor. (#280) ### Fixed diff --git a/resources/js/Pages/Forms/Edit.test.ts b/resources/js/Pages/Forms/Edit.test.ts index 74660edb..00704f77 100644 --- a/resources/js/Pages/Forms/Edit.test.ts +++ b/resources/js/Pages/Forms/Edit.test.ts @@ -4,6 +4,7 @@ import { AxiosResponse } from "axios"; import { callGetForm } from "@/api/forms"; import Edit from "./Edit.vue"; import FormMenu from "@/components/Factory/TopBar/FormMenu.vue"; +import PreviewFrame from "@/components/Factory/Preview/PreviewFrame.vue"; import { useForm, useWorkbench } from "@/stores"; vi.mock("@/api/forms", async (importOriginal) => ({ @@ -164,6 +165,26 @@ describe("editor loading", () => { expect(preview?.attributes("aria-haspopup")).toBe("dialog"); }); + it("closes the preview on Esc inside it and puts focus back on Preview", async () => { + const editor = await mountEditor(loads(blocks), { + stubs: { ...stubs, FormPreview: false, PreviewFrame: true }, + mocks: { route: () => "/" }, + }); + const preview = editor + .findAll("button") + .find((button) => button.text() === "Preview"); + await preview?.trigger("click"); + await flushPromises(); + expect(document.activeElement?.closest("[role=dialog]")).not.toBeNull(); + + // after a click into the frame, the form there gets the key and tells the editor + editor.findComponent(PreviewFrame).vm.$emit("escape"); + await flushPromises(); + + expect(document.querySelector("[role=dialog]")).toBeNull(); + expect(document.activeElement).toBe(preview?.element); + }); + it("puts focus back on the form menu when a preview opened from it closes", async () => { // the menu item is gone by then, so the dialog can't focus it again const editor = await mountEditor(loads(blocks), { diff --git a/resources/js/Pages/Forms/Settings.vue b/resources/js/Pages/Forms/Settings.vue index a8712d4a..e6e6fbc0 100644 --- a/resources/js/Pages/Forms/Settings.vue +++ b/resources/js/Pages/Forms/Settings.vue @@ -92,7 +92,7 @@ const navigation: Array<{ { name: "Options", component: Options, slug: "options" }, { name: "Privacy", component: Privacy, slug: "privacy" }, { - name: "Completion Page", + name: "End screen", component: CompletionPage, slug: "completion-page", }, diff --git a/resources/js/components/Factory/Document/EndScreenBlock.test.ts b/resources/js/components/Factory/Document/EndScreenBlock.test.ts index 1025d4b5..279e4262 100644 --- a/resources/js/components/Factory/Document/EndScreenBlock.test.ts +++ b/resources/js/components/Factory/Document/EndScreenBlock.test.ts @@ -8,6 +8,7 @@ import { useNotice } from "@/stores/notice"; import { useFreshForm } from "@/utils/useFreshForm"; import CompletionPage from "@/components/Factory/Settings/CompletionPage.vue"; import EndScreenBlock from "./EndScreenBlock.vue"; +import { endScreenOpen } from "./blockFocus"; vi.mock("@/api/forms", async (importOriginal) => ({ ...(await importOriginal()), @@ -30,6 +31,8 @@ let wrapper: VueWrapper | undefined; // real actions, not the stubs of the global testing pinia const mountBlock = () => { + // one at a time, as in the document: they share the open state + wrapper?.unmount(); wrapper = mount(EndScreenBlock, { attachTo: document.body, global: { plugins: [pinia], directives: { tooltip: {} } }, @@ -119,6 +122,15 @@ describe("the end screen in the document", () => { expect(block.find("#end-screen-headline").exists()).toBe(false); }); + it("tells the preview while it's open, and starts closed on the next page", async () => { + const block = await open(); + expect(endScreenOpen.value).toBe(true); + + block.unmount(); + wrapper = undefined; + expect(endScreenOpen.value).toBe(false); + }); + it("has the question card's phone sizes, number column and focus bar", async () => { const block = await open(); const [number, card] = block.findAll("#row-end-screen > div"); @@ -447,6 +459,15 @@ describe("the end screen in the document", () => { ); }); + // the public form only goes to a web link, else it shows the end screen + it("shows the end screen when the link isn't a web link", () => { + useForm().form = { ...form, use_cta_redirect: true, cta_link: "mailto:hi@example.com" }; + const block = mountBlock(); + + expect(block.text()).not.toContain("No end screen"); + expect(block.find("button").text()).toContain("See you on Thursday!"); + }); + it("stays in step with the Settings page through the form store", async () => { const block = await open(); await block.find("#end-screen-headline").setValue("Thanks!"); diff --git a/resources/js/components/Factory/Document/EndScreenBlock.vue b/resources/js/components/Factory/Document/EndScreenBlock.vue index 70eac0d6..d222d88c 100644 --- a/resources/js/components/Factory/Document/EndScreenBlock.vue +++ b/resources/js/components/Factory/Document/EndScreenBlock.vue @@ -8,12 +8,12 @@
No end screen: - people go to {{ store.form.cta_link }} + people go to {{ store.form?.cta_link }}
Change in Settings diff --git a/resources/js/components/Factory/Document/blockFocus.ts b/resources/js/components/Factory/Document/blockFocus.ts index c1be464c..24e3458e 100644 --- a/resources/js/components/Factory/Document/blockFocus.ts +++ b/resources/js/components/Factory/Document/blockFocus.ts @@ -4,6 +4,9 @@ import { useForm, useWorkbench } from "@/stores"; // the question whose card takes the cursor once it shows: its text editor isn't there at once export const cursorFor = ref{{ hidden }}, so the preview can't jump to it.
++ No end screen: people go to {{ redirect }}. +
Add a question to see it here.
-{{ hidden }}, so the preview can't jump to it.
++ No end screen: people go to {{ redirect }}. +
Add a question to see it here.
-