Skip to content

Commit a64d391

Browse files
committed
add stress test to repo
1 parent 756c350 commit a64d391

File tree

2 files changed

+195
-0
lines changed
  • packages/svelte

2 files changed

+195
-0
lines changed

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { current_batch } from '../../reactivity/batch.js';
4343

4444
// When making substantive changes to this file, validate them with the each block stress test:
4545
// https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b
46+
// This test also exists in this repo, as `packages/svelte/tests/manual/each-stress-test`
4647

4748
/**
4849
* @param {any} _
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<script lang="ts">
2+
import { tick } from 'svelte';
3+
4+
const VALUES = Array.from('abcdefghijklmnopqrstuvwxyz');
5+
6+
const presets = [
7+
// b is never destroyed
8+
[
9+
"ab",
10+
"",
11+
"a",
12+
"abc"
13+
],
14+
// the final state is 'abc', not 'cba'
15+
[
16+
"abc",
17+
"",
18+
"cba"
19+
],
20+
// the case in https://github.com/sveltejs/svelte/pull/17240
21+
[
22+
"abc",
23+
"adbc",
24+
"adebc"
25+
],
26+
[
27+
"ab",
28+
"a",
29+
"abc"
30+
],
31+
[
32+
"a",
33+
"bc",
34+
"bcd"
35+
],
36+
// add more presets by hitting 'party' and copying from the console
37+
];
38+
39+
function shuffle() {
40+
const values = VALUES.slice();
41+
const number = Math.floor(Math.random() * VALUES.length);
42+
let shuffled = '';
43+
for (let i = 0; i < number; i++) {
44+
shuffled += (values.splice(Math.floor(Math.random() * (number - i)), 1))[0];
45+
}
46+
47+
return shuffled;
48+
}
49+
50+
function mark(node) {
51+
let prev = -1;
52+
53+
return {
54+
duration: transition ? (slow ? 5000 : 500) : 0,
55+
tick(t) {
56+
const direction = t >= prev ? 'in' : 'out';
57+
node.style.color = direction === 'in' ? '' : 'grey';
58+
59+
prev = t;
60+
}
61+
}
62+
}
63+
64+
const record = [];
65+
66+
const sleep = (ms = slow ? 1000 : 100) => new Promise((f) => setTimeout(f, ms));
67+
68+
async function test(x: string) {
69+
console.group(JSON.stringify(x));
70+
error = null;
71+
72+
list = x;
73+
record.push(list);
74+
if (transition) {
75+
await sleep();
76+
} else {
77+
await tick();
78+
await tick();
79+
}
80+
check('reconcile');
81+
82+
n += 1;
83+
await tick();
84+
check('update');
85+
console.groupEnd();
86+
}
87+
88+
function check(task: string) {
89+
const expected = list.split('').map((c) => `(${c}:${n})`).join('') || '(fallback)';
90+
91+
const children = Array.from(container.children);
92+
const filtered = children.filter((span: HTMLElement) => !span.style.color);
93+
const received = filtered.map((span) => span.textContent).join('');
94+
95+
if (expected !== received) {
96+
console.log('expected:', expected);
97+
console.log('received:', received);
98+
console.log(JSON.stringify(record, null, ' '));
99+
100+
error = `failed to ${task}`;
101+
throw new Error(error);
102+
}
103+
}
104+
105+
let list = $state('');
106+
let n = $state(0);
107+
let error = $state(null);
108+
let slow = $state(false);
109+
let transition = $state(true);
110+
let partying = $state(false);
111+
112+
let container: HTMLElement;
113+
</script>
114+
115+
<h1>each block stress test</h1>
116+
117+
<label>
118+
<input type="checkbox" bind:checked={transition} />
119+
transition
120+
</label>
121+
122+
<label>
123+
<input type="checkbox" bind:checked={slow} />
124+
slow
125+
</label>
126+
127+
<fieldset>
128+
<legend>random</legend>
129+
130+
<button onclick={() => test(shuffle())}>test</button>
131+
<button onclick={async () => {
132+
if (partying) {
133+
partying = false;
134+
} else {
135+
partying = true;
136+
while (partying) await test(shuffle());
137+
}
138+
}}>{partying ? 'stop' : 'party'}</button>
139+
</fieldset>
140+
141+
<fieldset>
142+
<legend>presets</legend>
143+
144+
{#each presets as preset, index}
145+
<button onclick={async () => {
146+
for (let i = 0; i < preset.length; i += 1) {
147+
await test(preset[i]);
148+
}
149+
}}>{index + 1}</button>
150+
{/each}
151+
</fieldset>
152+
153+
<form onsubmit={(e) => {
154+
e.preventDefault();
155+
test(e.currentTarget.querySelector('input').value);
156+
}}>
157+
<fieldset>
158+
<legend>input</legend>
159+
<input />
160+
</fieldset>
161+
</form>
162+
163+
<div id="output" bind:this={container}>
164+
{#each list as c (c)}
165+
<span transition:mark>({c}:{n})</span>
166+
{:else}
167+
<span transition:mark>(fallback)</span>
168+
{/each}
169+
</div>
170+
171+
{#if error}
172+
<p class="error">{error}</p>
173+
{/if}
174+
175+
<style>
176+
fieldset {
177+
display: flex;
178+
gap: 0.5em;
179+
border-radius: 0.5em;
180+
corner-shape: squircle;
181+
margin: 0 0 1em 0;
182+
padding: 0.2em 0.8em 0.8em;
183+
}
184+
185+
legend {
186+
padding: 0.2em 0.5em;
187+
left: -0.2em;
188+
position: relative;
189+
}
190+
191+
.error {
192+
color: red;
193+
}
194+
</style>

0 commit comments

Comments
 (0)