|
| 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