From 14ae82872a0c0b30e05bcf1e87b75162cd9d7400 Mon Sep 17 00:00:00 2001 From: Simon Taranto Date: Wed, 3 Aug 2022 14:23:20 -0600 Subject: [PATCH] Add turbo:fetch-request-error event on frame and form network errors (#640) * Add turbo:fetch-error event on frame and form network errors closes https://github.com/hotwired/turbo/issues/462 This PR adds a new event called `turbo:fetch-error` that dispatches when a form or frame fetch request errors. This event will let developers respond appropriately when a fetch request fails due network errors (such as on flaky wifi). At the moment, if a frame navigation fetch request fails due to a network error an error is thrown so https://github.com/hotwired/turbo/blob/2d5cdda4c030658da21965cb20d2885ca7c3e127/src/http/fetch_request.ts#L107 never runs and therefore the `turbo:before-fetch-response` event is not dispatched. * Update src/core/frames/frame_controller.ts Co-authored-by: Keith Cirkel * Update src/core/drive/form_submission.ts Co-authored-by: Keith Cirkel * Add spec * Get browser name right * Fetch project name properly * Use page.context() * remove timeout on offline * Use first page to reduce go to calls * Use tabs again since behavior is different when using turbo-action * use broader event assertion * Update event name and export type * add new event to test helper * Limit recursion in the test helper * skip the details obj instead of limiting recursion Co-authored-by: Keith Cirkel --- src/core/drive/form_submission.ts | 5 +++++ src/core/frames/frame_controller.ts | 6 +++++- src/core/index.ts | 1 + src/core/session.ts | 2 ++ src/tests/fixtures/tabs.html | 2 +- src/tests/fixtures/test.js | 1 + src/tests/functional/frame_navigation_tests.ts | 7 +++++++ 7 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/core/drive/form_submission.ts b/src/core/drive/form_submission.ts index 6ff0d336e..d1676d2bd 100644 --- a/src/core/drive/form_submission.ts +++ b/src/core/drive/form_submission.ts @@ -3,6 +3,7 @@ import { FetchResponse } from "../../http/fetch_response" import { expandURL } from "../url" import { dispatch, getMetaContent } from "../../util" import { StreamMessage } from "../streams/stream_message" +import { TurboFetchRequestErrorEvent } from "../session" export interface FormSubmissionDelegate { formSubmissionStarted(formSubmission: FormSubmission): void @@ -199,6 +200,10 @@ export class FormSubmission { requestErrored(request: FetchRequest, error: Error) { this.result = { success: false, error } + dispatch("turbo:fetch-request-error", { + target: this.formElement, + detail: { request, error }, + }) this.delegate.formSubmissionErrored(this, error) } diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index d5ddd45fe..b3bb376fb 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -29,7 +29,7 @@ import { FrameRenderer } from "./frame_renderer" import { session } from "../index" import { isAction, Action } from "../types" import { VisitOptions } from "../drive/visit" -import { TurboBeforeFrameRenderEvent } from "../session" +import { TurboBeforeFrameRenderEvent, TurboFetchRequestErrorEvent } from "../session" import { StreamMessage } from "../streams/stream_message" export class FrameController @@ -247,6 +247,10 @@ export class FrameController requestErrored(request: FetchRequest, error: Error) { console.error(error) + dispatch("turbo:fetch-request-error", { + target: this.element, + detail: { request, error }, + }) this.resolveVisitPromise() } diff --git a/src/core/index.ts b/src/core/index.ts index 721d2989c..54b033821 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -19,6 +19,7 @@ export { TurboBeforeRenderEvent, TurboBeforeVisitEvent, TurboClickEvent, + TurboFetchRequestErrorEvent, TurboFrameLoadEvent, TurboFrameRenderEvent, TurboLoadEvent, diff --git a/src/core/session.ts b/src/core/session.ts index 992f49deb..a1a36fb1a 100644 --- a/src/core/session.ts +++ b/src/core/session.ts @@ -21,6 +21,7 @@ import { FrameElement } from "../elements/frame_element" import { FrameViewRenderOptions } from "./frames/frame_view" import { FetchResponse } from "../http/fetch_response" import { Preloader, PreloaderDelegate } from "./drive/preloader" +import { FetchRequest } from "../http/fetch_request" export type FormMode = "on" | "off" | "optin" export type TimingData = unknown @@ -30,6 +31,7 @@ export type TurboBeforeVisitEvent = CustomEvent<{ url: string }> export type TurboClickEvent = CustomEvent<{ url: string; originalEvent: MouseEvent }> export type TurboFrameLoadEvent = CustomEvent export type TurboBeforeFrameRenderEvent = CustomEvent<{ newFrame: FrameElement } & FrameViewRenderOptions> +export type TurboFetchRequestErrorEvent = CustomEvent<{ request: FetchRequest; error: Error }> export type TurboFrameRenderEvent = CustomEvent<{ fetchResponse: FetchResponse }> export type TurboLoadEvent = CustomEvent<{ url: string; timing: TimingData }> export type TurboRenderEvent = CustomEvent diff --git a/src/tests/fixtures/tabs.html b/src/tests/fixtures/tabs.html index cc9666c29..702cea0a1 100644 --- a/src/tests/fixtures/tabs.html +++ b/src/tests/fixtures/tabs.html @@ -1,5 +1,5 @@ - + Tabs diff --git a/src/tests/fixtures/test.js b/src/tests/fixtures/test.js index acb543061..b22b09248 100644 --- a/src/tests/fixtures/test.js +++ b/src/tests/fixtures/test.js @@ -48,6 +48,7 @@ "turbo:before-fetch-response", "turbo:visit", "turbo:before-frame-render", + "turbo:fetch-request-error", "turbo:frame-load", "turbo:frame-render", "turbo:reload" diff --git a/src/tests/functional/frame_navigation_tests.ts b/src/tests/functional/frame_navigation_tests.ts index 9dc1ae98c..a38e27b85 100644 --- a/src/tests/functional/frame_navigation_tests.ts +++ b/src/tests/functional/frame_navigation_tests.ts @@ -23,6 +23,13 @@ test("test frame navigation with exterior link", async ({ page }) => { await nextEventOnTarget(page, "frame", "turbo:frame-load") }) +test("test frame navigation emits fetch-request-error event when offline", async ({ page }) => { + await page.goto("/src/tests/fixtures/tabs.html") + await page.context().setOffline(true) + await page.click("#tab-2") + await nextEventNamed(page, "turbo:fetch-request-error") +}) + test("test promoted frame navigation updates the URL before rendering", async ({ page }) => { await page.goto("/src/tests/fixtures/tabs.html")