mirror of https://github.com/sveltejs/svelte
commit
e2c81d69f8
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: leave stale promises to wait for a later resolution, instead of rejecting
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: reapply context after transforming error during SSR
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: don't rebase just-created batches
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
chore: allow `null` for `pending` in typings
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: flush eager effects in production
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: rethrow error of failed iterable after calling `return()`
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: account for proxified instance when updating `bind:this`
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: ensure scheduled batch is flushed if not obsolete
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: resolve stale deriveds with latest value
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
chore: remove unnecessary `increment_pending` calls
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: correctly compile component member expressions for SSR
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: reset `source.updated` stack traces after `flush`
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"svelte": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: replacing async 'blocking' strategy with 'merging'
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: allow `@debug` tags to reference awaited variables
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: re-run fallback props if dependencies update
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: abort running obsolete async branches
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: ignore comments when reading CSS values
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: wrap `Promise.all` in `save` during SSR
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: ignore false-positive errors of `$inspect` dependencies
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { afterAll, beforeAll, expect, test } from 'vitest';
|
||||||
|
import { Renderer } from './renderer.js';
|
||||||
|
import type { Component } from 'svelte';
|
||||||
|
import { disable_async_mode_flag, enable_async_mode_flag } from '../flags/index.js';
|
||||||
|
import { hydratable } from './hydratable.js';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
enable_async_mode_flag();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
disable_async_mode_flag();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats replacement tokens in hydratable promise values as literals', async () => {
|
||||||
|
const component = (renderer: Renderer) => {
|
||||||
|
hydratable('key', () => Promise.resolve(`$'`));
|
||||||
|
renderer.child(async () => {
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
renderer.push('ok');
|
||||||
|
};
|
||||||
|
|
||||||
|
const { head } = await Renderer.render(component as unknown as Component);
|
||||||
|
const script_match = head.match(/<script(?:\s[^>]*)?>([\s\S]*)<\/script>/);
|
||||||
|
|
||||||
|
expect(script_match, 'expected hydratable script in head output').toBeTruthy();
|
||||||
|
|
||||||
|
const script_content = script_match![1];
|
||||||
|
expect(script_content).toContain('const h = (window.__svelte ??= {}).h ??= new Map();');
|
||||||
|
expect(script_content).toContain('r("$\'")');
|
||||||
|
expect(script_content).toMatch(/\[\s*"key"\s*,\s*r\("\$'"\)\s*\]/);
|
||||||
|
});
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { expect, test } from 'vitest';
|
||||||
|
import { REGEX_VALID_TAG_NAME } from './utils';
|
||||||
|
|
||||||
|
test('REGEX_VALID_TAG_NAME accepts common HTML tag names', () => {
|
||||||
|
const common_html_tag_names = ['div', 'span', 'button', 'input', 'svg', 'math', 'a'];
|
||||||
|
|
||||||
|
for (const tag_name of common_html_tag_names) {
|
||||||
|
expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('REGEX_VALID_TAG_NAME accepts basic custom element names', () => {
|
||||||
|
const valid_custom_tag_names = ['my-element', 'x-foo', 'todo-item', 'my-element2'];
|
||||||
|
|
||||||
|
for (const tag_name of valid_custom_tag_names) {
|
||||||
|
expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('REGEX_VALID_TAG_NAME accepts spec-allowed custom element characters', () => {
|
||||||
|
const valid_custom_tag_names = [
|
||||||
|
'x-foo.bar',
|
||||||
|
'x-foo_bar',
|
||||||
|
'x-foo\u00B7bar',
|
||||||
|
'x-foo\u00FCbar',
|
||||||
|
'x-foo\u{1F600}bar',
|
||||||
|
'x-'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const tag_name of valid_custom_tag_names) {
|
||||||
|
expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('REGEX_VALID_TAG_NAME rejects invalid tag names', () => {
|
||||||
|
const invalid_tag_names = ['', '1', 'x\u00FC', '-x-foo', '1foo', 'x-foo bar', 'x-foo/', 'x-foo>'];
|
||||||
|
|
||||||
|
for (const tag_name of invalid_tag_names) {
|
||||||
|
expect(REGEX_VALID_TAG_NAME.test(tag_name)).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('REGEX_VALID_TAG_NAME no ReDoS', () => {
|
||||||
|
const before = performance.now();
|
||||||
|
REGEX_VALID_TAG_NAME.test('a-----------------------------------!');
|
||||||
|
const after = performance.now();
|
||||||
|
if (after - before > 10) {
|
||||||
|
throw new Error(`REGEX_VALID_TAG_NAME is vulnerable to ReDoS`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { assert_ok, test } from '../../assert';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, waitUntil, window }) {
|
||||||
|
const form = target.querySelector('form');
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const [i1, i2, i3] = target.querySelectorAll('input');
|
||||||
|
assert_ok(form);
|
||||||
|
assert_ok(button);
|
||||||
|
|
||||||
|
assert.equal(form.id, 'initial-form');
|
||||||
|
assert.equal(form.className, 'first');
|
||||||
|
assert.equal(window.getComputedStyle(form).backgroundColor, 'rgb(255, 0, 0)');
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
await waitUntil(() => form.id === 'updated-form');
|
||||||
|
|
||||||
|
assert.equal(form.id, 'updated-form');
|
||||||
|
assert.equal(form.className, 'second');
|
||||||
|
assert.equal(i3.id, '', 'input clobbered form');
|
||||||
|
assert.equal(window.getComputedStyle(form).backgroundColor, 'rgb(0, 0, 255)');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
let form_attributes = $state({ id: 'initial-form' });
|
||||||
|
let class_name = $state('first');
|
||||||
|
let background_color = $state('rgb(255, 0, 0)');
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
form_attributes = { id: 'updated-form' };
|
||||||
|
class_name = 'second';
|
||||||
|
background_color = 'rgb(0, 0, 255)';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={update}>update</button>
|
||||||
|
|
||||||
|
<form {...form_attributes} class={class_name} style:background-color={background_color}>
|
||||||
|
<!-- the once non-symbol-ified hidden properties we used -->
|
||||||
|
<input {...{ name: '__className' }} value="x" />
|
||||||
|
<input {...{ name: '__style' }} value="y" />
|
||||||
|
<input {...{ name: '__attributes' }} value="z" />
|
||||||
|
</form>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
const [increment, resolve] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
increment.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<button>increment</button> <button>resolve</button> 0 <p>loading...</p>'
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
'<button>increment</button> <button>resolve</button> 1 <p>1</p>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
let count = $state(0);
|
||||||
|
|
||||||
|
const queued = [];
|
||||||
|
|
||||||
|
async function delay(v) {
|
||||||
|
if (!v) return v;
|
||||||
|
return new Promise(r => queued.push(() => r(v)));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => count++}>increment</button>
|
||||||
|
<button onclick={() => queued.shift()?.()}>resolve</button>
|
||||||
|
|
||||||
|
{await delay(count)}
|
||||||
|
{#if $state.eager(count) !== count}
|
||||||
|
<p>loading...</p>
|
||||||
|
{:else}
|
||||||
|
<p>{count}</p>
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
const [increment, resolve] = target.querySelectorAll('button');
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
increment.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`<button>increment</button> <button>resolve</button>
|
||||||
|
<ul><li>0 / 0</li><li>0 / loading...</li><li>0 / 0</li></ul>`
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`<button>increment</button> <button>resolve</button>
|
||||||
|
<ul><li>0 / 0</li><li>1 / 1</li><li>0 / 0</li></ul>`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
logs.some((l) => l.toString().includes('0 ') || l.toString().includes('2')),
|
||||||
|
false,
|
||||||
|
'only the second $state.eager should have been evaluated'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
let counts = $state([0, 0, 0]);
|
||||||
|
const queued = [];
|
||||||
|
async function delay(v) {
|
||||||
|
if (!v) return v;
|
||||||
|
return new Promise(r => queued.push(() => r(v)));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => counts[1]++}>increment</button>
|
||||||
|
<button onclick={() => queued.shift()?.()}>resolve</button>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{#each counts as count, i}
|
||||||
|
<li>
|
||||||
|
{await delay(count)} /
|
||||||
|
{#if console.log(i) || $state.eager(count) !== count}
|
||||||
|
loading...
|
||||||
|
{:else}
|
||||||
|
{count}
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
Loading…
Reference in new issue