Skip to content

Commit

Permalink
Expand Turbo.setFormMode guards (#658)
Browse files Browse the repository at this point in the history
Follow-up to #655
Follow-up to #419

This commit splits out a `form_mode_tests.ts` module separate from
`form_submission_tests.ts`, along with a `form_mode.html` fixture file.

Next, add test fixtures and test coverage for more thorough coverage of
form mode, namely scenarios that submit a form without a submitter (for
example, typing into an `<input type="text">` and pressing
<kbd>enter</kbd>.

Next, the `Turbo.setFormMode()` should be particular about its
argument's value. This commit introduces a `FormMode = "on" | "off" |
"optin"` type.

Finally, rename `submitterIsNavigatable` to `submissionIsNavigatable`,
and pass the `HTMLFormElement` and optional `HTMLElement` as a pair of
arguments.
  • Loading branch information
seanpdoyle authored Jul 31, 2022
1 parent 97ce0d7 commit d2443b6
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 52 deletions.
4 changes: 2 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Adapter } from "./native/adapter"
import { Session } from "./session"
import { FormMode, Session } from "./session"
import { Cache } from "./cache"
import { Locatable } from "./url"
import { StreamMessage } from "./streams/stream_message"
Expand Down Expand Up @@ -127,6 +127,6 @@ export function setConfirmMethod(confirmMethod: (message: string, element: HTMLF
FormSubmission.confirmMethod = confirmMethod
}

export function setFormMode(mode: string) {
export function setFormMode(mode: FormMode) {
session.setFormMode(mode)
}
32 changes: 16 additions & 16 deletions src/core/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FrameViewRenderOptions } from "./frames/frame_view"
import { FetchResponse } from "../http/fetch_response"
import { Preloader, PreloaderDelegate } from "./drive/preloader"

export type FormMode = "on" | "off" | "optin"
export type TimingData = unknown
export type TurboBeforeCacheEvent = CustomEvent
export type TurboBeforeRenderEvent = CustomEvent<{ newBody: HTMLBodyElement } & PageViewRenderOptions>
Expand Down Expand Up @@ -64,7 +65,7 @@ export class Session
enabled = true
progressBarDelay = 500
started = false
formMode = "on"
formMode: FormMode = "on"

start() {
if (!this.started) {
Expand Down Expand Up @@ -137,7 +138,7 @@ export class Session
this.progressBarDelay = delay
}

setFormMode(mode: string) {
setFormMode(mode: FormMode) {
this.formMode = mode
}

Expand Down Expand Up @@ -229,8 +230,7 @@ export class Session
const action = getAction(form, submitter)

return (
this.elementIsNavigatable(form) &&
(!submitter || this.submitterIsNavigatable(submitter)) &&
this.submissionIsNavigatable(form, submitter) &&
locationIsVisitable(expandURL(action), this.snapshot.rootLocation)
)
}
Expand Down Expand Up @@ -382,19 +382,23 @@ export class Session

// Helpers

submitterIsNavigatable(element: Element) {
submissionIsNavigatable(form: HTMLFormElement, submitter?: HTMLElement): boolean {
if (this.formMode == "off") {
return false
} else {
const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true

if (this.formMode == "optin") {
return submitterIsNavigatable && form.closest('[data-turbo="true"]') != null
} else {
return submitterIsNavigatable && this.elementIsNavigatable(form)
}
}
if (this.formMode == "optin" && hasForm(element) && element.form) {
return element.form.closest('[data-turbo="true"]') != null
}
return this.elementIsNavigatable(element)
}

elementIsNavigatable(element?: Element) {
const container = element?.closest("[data-turbo]")
const withinFrame = element?.closest("turbo-frame")
elementIsNavigatable(element: Element): boolean {
const container = element.closest("[data-turbo]")
const withinFrame = element.closest("turbo-frame")

// Check if Drive is enabled on the session or we're within a Frame.
if (this.drive || withinFrame) {
Expand Down Expand Up @@ -448,7 +452,3 @@ const deprecatedLocationPropertyDescriptors = {
},
},
}

function hasForm(element: Element): element is Element & { form: HTMLFormElement | null } {
return "form" in element
}
6 changes: 0 additions & 6 deletions src/tests/fixtures/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ <h1>Form</h1>
<input type="submit">
<button type="submit" name="greeting" id="secondary_submitter" data-turbo-confirm="Are you really sure?" formaction="/__turbo/redirect?path=/src/tests/fixtures/one.html" formmethod="post" value="secondary_submitter">Secondary action</button>
</form>
<form id="turbo-enabled-form" action="/__turbo/submit" method="post" data-turbo="true" class="turbo-enabled">
<input type="hidden" name="query" value="2">
<input type="submit">
</form>

<button form="turbo-enabled-form">Submit #turbo-enabled-form</button>
</div>
<hr>
<div id="no-action">
Expand Down
44 changes: 44 additions & 0 deletions src/tests/fixtures/form_mode.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form</title>
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
<script src="/src/tests/fixtures/test.js"></script>
<script>
const params = new URLSearchParams(window.location.search)

if (params.has("formMode")) {
window.Turbo.setFormMode(params.get("formMode"))
}
</script>
</head>
<body>
<h1>Form Mode</h1>

<form id="form" action="/__turbo/redirect" method="post">
<input type="hidden" name="path" value="/src/tests/fixtures/form.html">
<input type="hidden" name="greeting" value="Hello from a form">
<button>submit #form</button>
</form>

<form id="form-without-submitter" action="/__turbo/redirect" method="post">
<input type="hidden" name="path" value="/src/tests/fixtures/form.html">
<input type="text" name="greeting" value="Hello from a form">
</form>

<form id="turbo-enabled-form" action="/__turbo/redirect" method="post" data-turbo="true">
<input type="hidden" name="path" value="/src/tests/fixtures/form.html">
<input type="hidden" name="greeting" value="Hello from a form[data-turbo=true]">
<button>submit #turbo-enabled-form</button>
</form>

<form id="turbo-enabled-form-without-submitter" action="/__turbo/redirect" method="post" data-turbo="true">
<input type="hidden" name="path" value="/src/tests/fixtures/form.html">
<input type="text" name="greeting" value="Hello from a form[data-turbo=true]">
</form>

<button form="form">Submit #form</button>
<button form="turbo-enabled-form">Submit #turbo-enabled-form</button>
</body>
</html>
75 changes: 75 additions & 0 deletions src/tests/functional/form_mode_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Page, test } from "@playwright/test"
import { getFromLocalStorage, setLocalStorageFromEvent } from "../helpers/page"
import { assert } from "chai"

test("test form submission with form mode off", async ({ page }) => {
await gotoPageWithFormMode(page, "off")
await page.click("#turbo-enabled-form button")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission without submitter with form mode off", async ({ page }) => {
await gotoPageWithFormMode(page, "off")
await page.press("#turbo-enabled-form-without-submitter [type=text]", "Enter")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode off from submitter outside form", async ({ page }) => {
await gotoPageWithFormMode(page, "off")
await page.click("button[form=turbo-enabled-form]")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form not enabled", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.click("#form button")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission without submitter with form mode optin and form not enabled", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.press("#form-without-submitter [type=text]", "Enter")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form not enabled from submitter outside form", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.click("button[form=form]")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form enabled", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.click("#turbo-enabled-form button")

assert.ok(await formSubmitStarted(page))
})

test("test form submission without submitter with form mode optin and form enabled", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.press("#turbo-enabled-form-without-submitter [type=text]", "Enter")

assert.ok(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form enabled from submitter outside form", async ({ page }) => {
await gotoPageWithFormMode(page, "optin")
await page.click("button[form=turbo-enabled-form]")

assert.ok(await formSubmitStarted(page))
})

async function gotoPageWithFormMode(page: Page, formMode: "on" | "off" | "optin") {
await page.goto(`/src/tests/fixtures/form_mode.html?formMode=${formMode}`)
await setLocalStorageFromEvent(page, "turbo:submit-start", "formSubmitStarted", "true")
}

function formSubmitStarted(page: Page) {
return getFromLocalStorage(page, "formSubmitStarted")
}
28 changes: 0 additions & 28 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,34 +1016,6 @@ test("test stream link method form submission within form outside frame", async
assert.equal(await page.textContent("#frame div.message"), "Link!")
})

test("test form submission with form mode off", async ({ page }) => {
await page.evaluate(() => window.Turbo.setFormMode("off"))
await page.click("#standard form.turbo-enabled input[type=submit]")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form not enabled", async ({ page }) => {
await page.evaluate(() => window.Turbo.setFormMode("optin"))
await page.click("#standard form.redirect input[type=submit]")

assert.notOk(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form enabled", async ({ page }) => {
await page.evaluate(() => window.Turbo.setFormMode("optin"))
await page.click("#standard form.turbo-enabled input[type=submit]")

assert.ok(await formSubmitStarted(page))
})

test("test form submission with form mode optin and form enabled from submitter outside form", async ({ page }) => {
await page.evaluate(() => window.Turbo.setFormMode("optin"))
await page.click("#standard button[form=turbo-enabled-form]")

assert.ok(await formSubmitStarted(page))
})

test("test turbo:before-fetch-request fires on the form element", async ({ page }) => {
await page.click('#targets-frame form.one [type="submit"]')
assert.ok(await nextEventOnTarget(page, "form_one", "turbo:before-fetch-request"))
Expand Down

0 comments on commit d2443b6

Please sign in to comment.