From b9bc188175c9d46e57765adfcc2194854c18c5a8 Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Thu, 16 Feb 2023 20:14:39 +0100 Subject: [PATCH] Fix `data-turbo-confirm` on `` without `data-turbo-method` --- src/core/confirmation.ts | 5 +++++ src/core/drive/form_submission.ts | 11 ++--------- src/core/index.ts | 6 +++--- src/core/session.ts | 11 ++++++++++- src/tests/functional/visit_tests.ts | 25 +++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 src/core/confirmation.ts diff --git a/src/core/confirmation.ts b/src/core/confirmation.ts new file mode 100644 index 000000000..ac7329467 --- /dev/null +++ b/src/core/confirmation.ts @@ -0,0 +1,5 @@ +export class Confirmation { + static confirmMethod(message: string, _element: Element, _submitter: Element | undefined): Promise { + return Promise.resolve(confirm(message)) + } +} diff --git a/src/core/drive/form_submission.ts b/src/core/drive/form_submission.ts index 1cfc1a247..343c9f8c8 100644 --- a/src/core/drive/form_submission.ts +++ b/src/core/drive/form_submission.ts @@ -1,5 +1,6 @@ import { FetchRequest, FetchMethod, fetchMethodFromString } from "../../http/fetch_request" import { FetchResponse } from "../../http/fetch_response" +import { Confirmation } from "../confirmation" import { expandURL } from "../url" import { dispatch, getAttribute, getMetaContent, hasAttribute } from "../../util" import { StreamMessage } from "../streams/stream_message" @@ -56,14 +57,6 @@ export class FormSubmission { state = FormSubmissionState.initialized result?: FormSubmissionResult - static confirmMethod( - message: string, - _element: HTMLFormElement, - _submitter: HTMLElement | undefined - ): Promise { - return Promise.resolve(confirm(message)) - } - constructor( delegate: FormSubmissionDelegate, formElement: HTMLFormElement, @@ -126,7 +119,7 @@ export class FormSubmission { const confirmationMessage = getAttribute("data-turbo-confirm", this.submitter, this.formElement) if (typeof confirmationMessage === "string") { - const answer = await FormSubmission.confirmMethod(confirmationMessage, this.formElement, this.submitter) + const answer = await Confirmation.confirmMethod(confirmationMessage, this.formElement, this.submitter) if (!answer) { return } diff --git a/src/core/index.ts b/src/core/index.ts index 7b7507c3f..75b351a04 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,6 +1,7 @@ import { Adapter } from "./native/adapter" import { FormMode, Session } from "./session" import { Cache } from "./cache" +import { Confirmation } from "./confirmation" import { Locatable } from "./url" import { StreamMessage } from "./streams/stream_message" import { StreamSource } from "./types" @@ -8,7 +9,6 @@ import { VisitOptions } from "./drive/visit" import { PageRenderer } from "./drive/page_renderer" import { PageSnapshot } from "./drive/page_snapshot" import { FrameRenderer } from "./frames/frame_renderer" -import { FormSubmission } from "./drive/form_submission" const session = new Session() const cache = new Cache(session) @@ -125,9 +125,9 @@ export function setProgressBarDelay(delay: number) { } export function setConfirmMethod( - confirmMethod: (message: string, element: HTMLFormElement, submitter: HTMLElement | undefined) => Promise + confirmMethod: (message: string, element: HTMLElement, submitter: HTMLElement | undefined) => Promise ) { - FormSubmission.confirmMethod = confirmMethod + Confirmation.confirmMethod = confirmMethod } export function setFormMode(mode: FormMode) { diff --git a/src/core/session.ts b/src/core/session.ts index d988ef3bb..6b909da3f 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -1,6 +1,7 @@ import { Adapter } from "./native/adapter" import { BrowserAdapter, ReloadReason } from "./native/browser_adapter" import { CacheObserver } from "../observers/cache_observer" +import { Confirmation } from "./confirmation" import { FormSubmitObserver, FormSubmitObserverDelegate } from "../observers/form_submit_observer" import { FrameRedirector } from "./frames/frame_redirector" import { History, HistoryDelegate } from "./drive/history" @@ -191,9 +192,17 @@ export class Session ) } - followedLinkToLocation(link: Element, location: URL) { + async followedLinkToLocation(link: Element, location: URL) { const action = this.getActionForLink(link) const acceptsStreamResponse = link.hasAttribute("data-turbo-stream") + const confirmationMessage = link.getAttribute("data-turbo-confirm") + + if (typeof confirmationMessage === "string") { + const answer = await Confirmation.confirmMethod(confirmationMessage, link, link) + if (!answer) { + return + } + } this.visit(location.href, { action, acceptsStreamResponse }) } diff --git a/src/tests/functional/visit_tests.ts b/src/tests/functional/visit_tests.ts index 146bcda58..d8b082444 100644 --- a/src/tests/functional/visit_tests.ts +++ b/src/tests/functional/visit_tests.ts @@ -223,3 +223,28 @@ test("test Visit with network error", async ({ page }) => { async function visitLocation(page: Page, location: string) { return page.evaluate((location) => window.Turbo.visit(location), location) } + +test("test data-turbo-confirm on anchor element without data-turbo-method", async ({ page }) => { + let confirmed = false + + page.on("dialog", (alert) => { + assert.equal(alert.message(), "Are you sure?") + alert.accept() + confirmed = true + }) + + await page.evaluate(() => { + const link = document.querySelector("#same-origin-link") + + if (link) link.dataset.turboConfirm = "Are you sure?" + }) + + assert.equal(await page.locator("#same-origin-link[data-turbo-confirm]:not([data-turbo-method])").count(), 1) + assert.equal(pathname(page.url()), "/src/tests/fixtures/visit.html") + + await page.click("#same-origin-link") + await nextEventNamed(page, "turbo:load") + + assert.isTrue(confirmed) + assert.equal(pathname(page.url()), "/src/tests/fixtures/one.html") +})