mirror of https://github.com/sveltejs/svelte
fix: keep the current selection of a `<select>` when its `defaultValue` is applied (#18719)
Also ensure init_select isn't called multiple times Follow-up to #18591.pull/18722/head
parent
4d5139552d
commit
864de81bb6
@ -0,0 +1,52 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { ok, test } from '../../test';
|
||||
|
||||
export default test({
|
||||
async test({ assert, target }) {
|
||||
const [nothing, unmatched, spread, late, touched] = target.querySelectorAll('select');
|
||||
const [change_default, change_spread, load, add] = target.querySelectorAll('button');
|
||||
const p = target.querySelector('p');
|
||||
ok(p);
|
||||
|
||||
assert.equal(nothing.selectedIndex, -1);
|
||||
assert.equal(unmatched.selectedIndex, -1);
|
||||
assert.equal(spread.selectedIndex, -1);
|
||||
assert.equal(late.selectedIndex, -1);
|
||||
assert.equal(touched.value, 'b');
|
||||
|
||||
change_default.click();
|
||||
change_spread.click();
|
||||
flushSync();
|
||||
|
||||
assert.equal(nothing.selectedIndex, -1);
|
||||
assert.equal(unmatched.selectedIndex, -1);
|
||||
assert.equal(spread.selectedIndex, -1);
|
||||
assert.equal(spread.className, 'two');
|
||||
assert.htmlEqual(p.innerHTML, 'zzz null');
|
||||
assert.deepEqual(
|
||||
[...nothing.options].map((option) => option.defaultSelected),
|
||||
[true, false]
|
||||
);
|
||||
|
||||
// a default change never moves the current selection
|
||||
assert.equal(touched.value, 'b');
|
||||
|
||||
// a default whose option arrives later selects it
|
||||
load.click();
|
||||
flushSync();
|
||||
await Promise.resolve();
|
||||
assert.equal(late.value, 'b');
|
||||
|
||||
// a user selection survives option mutations and default changes
|
||||
touched.options[2].selected = true;
|
||||
touched.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
add.click();
|
||||
flushSync();
|
||||
await Promise.resolve();
|
||||
assert.equal(touched.value, 'c');
|
||||
assert.deepEqual(
|
||||
[...touched.options].map((option) => option.defaultSelected),
|
||||
[true, false, false, false]
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,37 @@
|
||||
<script>
|
||||
let unmatched = $state('zzz');
|
||||
let nothing = $state(null);
|
||||
let defaultValue = $state('b');
|
||||
let props = $state({ defaultValue: 'b', class: 'one' });
|
||||
let late = $state([]);
|
||||
let options = $state(['a', 'b', 'c']);
|
||||
</script>
|
||||
|
||||
<select value={null} {defaultValue}>
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
|
||||
<select bind:value={unmatched} {defaultValue}>
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
|
||||
<select {...props} bind:value={nothing}>
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
|
||||
<select defaultValue="b">
|
||||
{#each late as option}<option value={option}>{option}</option>{/each}
|
||||
</select>
|
||||
|
||||
<select {defaultValue}>
|
||||
{#each options as option}<option value={option}>{option}</option>{/each}
|
||||
</select>
|
||||
|
||||
<button onclick={() => (defaultValue = 'a')}>change default</button>
|
||||
<button onclick={() => (props.class = 'two')}>change class</button>
|
||||
<button onclick={() => (late = ['a', 'b', 'c'])}>load options</button>
|
||||
<button onclick={() => options.push('d')}>add option</button>
|
||||
<p>{unmatched} {String(nothing)}</p>
|
||||
@ -0,0 +1,30 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
let observers = 0;
|
||||
const MutationObserver = globalThis.MutationObserver;
|
||||
|
||||
export default test({
|
||||
before_test() {
|
||||
observers = 0;
|
||||
globalThis.MutationObserver = class extends MutationObserver {
|
||||
/** @param {MutationCallback} callback */
|
||||
constructor(callback) {
|
||||
super(callback);
|
||||
observers++;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
after_test() {
|
||||
globalThis.MutationObserver = MutationObserver;
|
||||
},
|
||||
|
||||
test({ assert, target }) {
|
||||
const selects = target.querySelectorAll('select');
|
||||
for (const select of selects) {
|
||||
assert.equal(select.selectedIndex, 0);
|
||||
assert.equal(select.options[1].defaultSelected, true);
|
||||
}
|
||||
assert.equal(observers, selects.length);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,21 @@
|
||||
<script>
|
||||
let value = $state('a');
|
||||
let bound = $state('a');
|
||||
let spread = $state('a');
|
||||
let props = { defaultValue: 'b' };
|
||||
</script>
|
||||
|
||||
<select {value} defaultValue="b">
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
|
||||
<select bind:value={bound} defaultValue="b">
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
|
||||
<select {...props} bind:value={spread}>
|
||||
<option value="a">A</option>
|
||||
<option value="b">B</option>
|
||||
</select>
|
||||
Loading…
Reference in new issue