Repository navigation
Keep the right radios checked when forms move, leave or are renamed #185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ebf50bc
Keep radios checked when a form holding a radio with a form attribute…
claude 6356c72
Let a later radio win when a radio checked again at settle joined its…
claude e5e63a5
Test a moved form whose radio names a missing form (fuzzer seed 778738)
claude e8f4b0b
Keep restored radios following their checked attribute, and cover liv…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import { expect, onTestFinished, test } from "vitest" | ||
| import { morph, morphInner } from "../../src/morphlex" | ||
|
|
||
| // Chromium and Firefox briefly reset the form of a radio with a `form` attribute while an ancestor of it moves. | ||
| // A checked one then joins another group for that moment and unchecks the radio there. | ||
|
|
||
| function mount(html: string): HTMLElement { | ||
| const host = document.createElement("section") | ||
| host.innerHTML = html | ||
| document.body.append(host) | ||
| onTestFinished(() => host.remove()) | ||
| return host | ||
| } | ||
|
|
||
| function checked(host: Element, selector: string): boolean { | ||
| return host.querySelector<HTMLInputElement>(selector)!.checked | ||
| } | ||
|
|
||
| test("moving a form holding a checked radio of another form keeps the user's pick in the moved form", () => { | ||
| const host = mount( | ||
| `<form id="f1"><input type="radio" name="b" value="1" id="c0"><input type="radio" name="b" value="2" checked form="f2"></form><form id="f2"></form><p></p>`, | ||
| ) | ||
| host.querySelector<HTMLInputElement>("#c0")!.checked = true | ||
|
|
||
| morph( | ||
| host, | ||
| `<section><form id="f2"></form><p></p><form id="f1"><input type="radio" name="b" value="1" id="c0"><input type="radio" name="b" value="2" checked form="f2"></form></section>`, | ||
| { preserveChanges: true }, | ||
| ) | ||
|
|
||
| expect(checked(host, "#c0")).toBe(true) | ||
| expect(checked(host, "input[value='2']")).toBe(true) | ||
| }) | ||
|
|
||
| test("moving a form holding a checked radio of a missing form keeps the user's pick in the moved form", () => { | ||
| const form = `<form id="f1"><input type="radio" class="x" value="1" name="a"><input id="c0" value="3" checked name="a" type="radio" form="missing"></form>` | ||
| const host = mount(`<select id="c1" name="u"><option>3</option></select>${form}`) | ||
| host.querySelector<HTMLInputElement>("input.x")!.checked = true | ||
|
|
||
| morph(host, `<section>${form}<select id="c1" name="u"><option>3</option></select></section>`, { preserveChanges: true }) | ||
|
|
||
| expect(checked(host, "input.x")).toBe(true) | ||
| expect(checked(host, "#c0")).toBe(true) | ||
| }) | ||
|
|
||
| test("moving a form holding a radio the user checked that names the form keeps a formless radio checked", () => { | ||
| const html = `<input type="radio" name="a" value="3" checked id="c0"><p></p>` | ||
| const form = `<form id="f1"><input type="radio" name="a" value="1" form="f1" id="c2"></form>` | ||
|
|
||
| for (const preserveChanges of [false, true]) { | ||
| const host = mount(`${html}${form}`) | ||
| host.querySelector<HTMLInputElement>("#c2")!.checked = true | ||
|
|
||
| morph(host, `<section>${form}${html}</section>`, { preserveChanges }) | ||
|
|
||
| expect(checked(host, "#c0")).toBe(true) | ||
| expect(checked(host, "#c2")).toBe(preserveChanges) | ||
| } | ||
| }) | ||
|
|
||
| test("moving a form holding a checked radio that names the form keeps an untouched formless radio checked", () => { | ||
| const form = `<form id="f1"><input form="f1" name="a" value="3" type="radio" checked></form>` | ||
| const radio = `<input type="radio" checked name="a" value="1">` | ||
| const host = mount(`<input name="u" type="email">${form}${radio}`) | ||
| host.querySelector<HTMLInputElement>("input[type=email]")!.value = "typed" | ||
|
|
||
| morph(host, `<section>${form}<input name="u" type="email">${radio}</section>`, { preserveChanges: true }) | ||
|
|
||
| expect(checked(host, "input[value='1']")).toBe(true) | ||
| expect(checked(host, "input[value='3']")).toBe(true) | ||
| }) | ||
|
|
||
| test("moving a form holding a radio the user checked that names the form keeps a radio of a missing form checked", () => { | ||
| const radio = `<input id="c0" type="radio" name="a" form="missing" checked>` | ||
| const form = `<form id="f1"><input type="radio" name="a" value="1" form="f1" id="c2"></form>` | ||
| const host = mount(`${radio}<p></p>${form}`) | ||
| host.querySelector<HTMLInputElement>("#c2")!.checked = true | ||
|
|
||
| morph(host, `<section>${form}${radio}<p></p></section>`) | ||
|
|
||
| expect(checked(host, "#c0")).toBe(true) | ||
| expect(checked(host, "#c2")).toBe(false) | ||
| }) | ||
|
|
||
| test("removing a form holding a radio the user checked that names the form keeps a formless radio checked", () => { | ||
| const html = `<input type="radio" name="b" checked id="c1">` | ||
| const host = mount(`${html}<form id="f1"><input type="radio" name="b" form="f1" id="r6"></form>`) | ||
| host.querySelector<HTMLInputElement>("#r6")!.checked = true | ||
|
|
||
| morph(host, `<section>${html}</section>`) | ||
|
|
||
| expect(checked(host, "#c1")).toBe(true) | ||
| }) | ||
|
|
||
| test("moving a form holding a radio the user checked that names a missing form keeps the form's radio checked", () => { | ||
| const form = `<form><input id="c2" name="a" type="radio" checked value="2"><input id="c4" name="a" type="radio" form="missing" value="3"></form>` | ||
| const checkbox = `<input id="c3" type="checkbox" checked>` | ||
| const host = mount(`${checkbox}${form}`) | ||
| host.querySelector<HTMLInputElement>("#c4")!.checked = true | ||
|
|
||
| morph(host, `<section>${form}${checkbox}</section>`) | ||
|
|
||
| expect(checked(host, "#c2")).toBe(true) | ||
| expect(checked(host, "#c4")).toBe(false) | ||
| }) | ||
|
|
||
| test("an untouched radio with a form attribute in a moved form still follows its checked attribute", () => { | ||
| const form = `<form id="f1"><input type="radio" name="a" form="f1" checked id="r"></form>` | ||
| const host = mount(`<p></p><p></p>${form}`) | ||
|
|
||
| morph(host, `<section>${form}<p></p><p></p></section>`) | ||
| host.querySelector("#r")!.removeAttribute("checked") | ||
|
|
||
| expect(checked(host, "#r")).toBe(false) | ||
| }) | ||
|
|
||
| test("inserting a live form holding radios the user checked keeps them checked when one names a missing form", () => { | ||
| for (const preserveChanges of [false, true]) { | ||
| const host = mount( | ||
| `<div id="root"></div><div id="source"><form><input id="c2" name="a" type="radio"><input id="c4" name="a" type="radio" form="missing"></form></div>`, | ||
| ) | ||
| host.querySelector<HTMLInputElement>("#c2")!.checked = true | ||
| host.querySelector<HTMLInputElement>("#c4")!.checked = true | ||
|
|
||
| morphInner(host.querySelector("#root")!, host.querySelector("#source")!, { preserveChanges }) | ||
|
|
||
| expect(checked(host, "#c2")).toBe(true) | ||
| expect(checked(host, "#c4")).toBe(true) | ||
| } | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { expect, onTestFinished, test } from "vitest" | ||
| import { morph } from "../../src/morphlex" | ||
|
|
||
| // A checked radio that a form id change moves into another group joins it as when parsing, so the last | ||
| // radio the markup checks there stays checked. | ||
|
|
||
| test("a user-checked radio moved into another group by a form id change leaves that group's later radio checked", () => { | ||
| const host = document.createElement("section") | ||
| host.innerHTML = `<form id="f1"><input checked name="a" form="f2" type="radio" value="3" id="c1"></form><input value="3" checked id="c3" type="radio" name="a"><form id="f2"></form>` | ||
| document.body.append(host) | ||
| onTestFinished(() => host.remove()) | ||
| host.querySelector<HTMLInputElement>("#c1")!.checked = true | ||
|
|
||
| morph( | ||
| host, | ||
| `<section><form id="f1"><input form="f2" id="c1" name="a" value="3" type="radio" checked></form><input name="a" value="3" checked id="c3" type="radio"><form id="f9"></form></section>`, | ||
| ) | ||
|
|
||
| expect(host.querySelector<HTMLInputElement>("#c1")!.checked).toBe(false) | ||
| expect(host.querySelector<HTMLInputElement>("#c3")!.checked).toBe(true) | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.