Skip to content

Commit

Permalink
Preserve input values in cache
Browse files Browse the repository at this point in the history
Re-submission of #70

This change will save the values of `<input>`s, `<textarea>`s, and
`<select>`s when creating a snapshot so that when you navigate back or
forward in history, form fields will not be reset.

This behavior was already present prior to [5dfc79e][] for `<input>`s
and `<textarea>`s but not `<select>`s (see
[turbolinks/turbolinks#238][]).

[5dfc79e]: 5dfc79e
[turbolinks/turbolinks#238]: turbolinks/turbolinks#238
  • Loading branch information
entretechno-jeremiah authored and seanpdoyle committed Aug 5, 2022
1 parent bff30b3 commit 712c268
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
17 changes: 16 additions & 1 deletion src/core/drive/page_snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,22 @@ export class PageSnapshot extends Snapshot<HTMLBodyElement> {
}

clone() {
return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot)
const clonedElement = this.element.cloneNode(true)

const selectElements = this.element.querySelectorAll("select")
const clonedSelectElements = clonedElement.querySelectorAll("select")

for (const [index, source] of selectElements.entries()) {
for (const [optionIndex, option] of Array.from(source.options).entries()) {
clonedSelectElements[index].options[optionIndex].selected = option.selected
}
}

for (const clonedPasswordInput of clonedElement.querySelectorAll<HTMLInputElement>('input[type="password"]')) {
clonedPasswordInput.value = ""
}

return new PageSnapshot(clonedElement, this.headSnapshot)
}

get headElement() {
Expand Down
18 changes: 18 additions & 0 deletions src/tests/fixtures/rendering.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ <h1>Rendering</h1>
<p><a id="permanent-in-frame-element-link" href="/src/tests/fixtures/permanent_element.html" data-turbo-frame="frame">Permanent element in frame</a></p>
<p><a id="permanent-in-frame-without-layout-element-link" href="/src/tests/fixtures/frames/without_layout.html" data-turbo-frame="frame">Permanent element in frame without layout</a></p>
<p><a id="delayed-link" href="/__turbo/delayed_response">Delayed link</a></p>
<form>
<input type="text" id="text-input">
<input type="radio" id="radio-input">
<input type="checkbox" id="checkbox-input">
<textarea id="textarea"></textarea>
<select id="select">
<option value="1">1</option>
<option value="2">2</option>
</select>
<select id="select-multiple" multiple>
<option value="1">1</option>
<option value="2">2</option>
</select>

<input type="password" id="password-input">

<input type="reset" id="reset-input">
</form>
</section>
<div id="permanent" data-turbo-permanent>Rendering</div>

Expand Down
58 changes: 58 additions & 0 deletions src/tests/functional/rendering_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
nextBody,
nextEventNamed,
pathname,
propertyForSelector,
readEventLogs,
scrollToSelector,
selectorHasFocus,
sleep,
Expand All @@ -19,6 +21,7 @@ import {
test.beforeEach(async ({ page }) => {
await page.goto("/src/tests/fixtures/rendering.html")
await clearLocalStorage(page)
await readEventLogs(page)
})

test("test triggers before-render and render events", async ({ page }) => {
Expand Down Expand Up @@ -392,6 +395,61 @@ test("test preserves permanent element video playback", async ({ page }) => {
assert.equal(timeAfterRender, timeBeforeRender, "element state is preserved")
})

test("test preserves input values", async ({ page }) => {
await page.fill("#text-input", "test")
await page.click("#checkbox-input")
await page.click("#radio-input")
await page.fill("#textarea", "test")
await page.selectOption("#select", "2")
await page.selectOption("#select-multiple", "2")

await page.click("#same-origin-link")
await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextEventNamed(page, "turbo:load")

assert.equal(await propertyForSelector(page, "#text-input", "value"), "test")
assert.equal(await propertyForSelector(page, "#checkbox-input", "checked"), true)
assert.equal(await propertyForSelector(page, "#radio-input", "checked"), true)
assert.equal(await propertyForSelector(page, "#textarea", "value"), "test")
assert.equal(await propertyForSelector(page, "#select", "value"), "2")
assert.equal(await propertyForSelector(page, "#select-multiple", "value"), "2")
})

test("test does not preserve password values", async ({ page }) => {
await page.fill("#password-input", "test")

await page.click("#same-origin-link")
await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextEventNamed(page, "turbo:load")

assert.equal(await propertyForSelector(page, "#password-input", "value"), "")
})

test("test <input type='reset'> clears values when restored from cache", async ({ page }) => {
await page.fill("#text-input", "test")
await page.click("#checkbox-input")
await page.click("#radio-input")
await page.fill("#textarea", "test")
await page.selectOption("#select", "2")
await page.selectOption("#select-multiple", "2")

await page.click("#same-origin-link")
await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextEventNamed(page, "turbo:load")

await page.click("#reset-input")

assert.equal(await propertyForSelector(page, "#text-input", "value"), "")
assert.equal(await propertyForSelector(page, "#checkbox-input", "checked"), false)
assert.equal(await propertyForSelector(page, "#radio-input", "checked"), false)
assert.equal(await propertyForSelector(page, "#textarea", "value"), "")
assert.equal(await propertyForSelector(page, "#select", "value"), "1")
assert.equal(await propertyForSelector(page, "#select-multiple", "value"), "")
})

test("test before-cache event", async ({ page }) => {
await page.evaluate(() => {
addEventListener("turbo:before-cache", () => (document.body.innerHTML = "Modified"), { once: true })
Expand Down

0 comments on commit 712c268

Please sign in to comment.