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