Skip to content

Commit 6a4aa00

Browse files
test(web): cover streamed fallbacks with dynamic text holes in parity harness
Regression scenario for solid#2877 (fixed in dom-expressions#542): a Loading fallback containing dynamic text holes serializes with comment separators inside the swap range, so $df must scan to its own matching pl marker instead of stopping at the first comment. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9b4dd76 commit 6a4aa00

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "dynamic-fallback-stream",
3+
"shell": "<div _hk=1><span>status </span><!--$--><template id=\"pl-2\"></template>preparing <!--!$-->42<!--!$-->% done<!--pl-2--><!--/--></div><script>(self.$R=self.$R||{})[\"\"]=[];_$HY.r[\"0\"]=$R[0]=($R[1]=($R[2]=() => {\n const resolver = {\n p: 0,\n s: 0,\n f: 0\n };\n resolver.p = new Promise((resolve, reject) => {\n resolver.s = resolve;\n resolver.f = reject;\n });\n return resolver;\n})()).p;_$HY.r[\"2_fr\"]=$R[3]=($R[4]=$R[2]()).p;</script>",
4+
"rest": "<template id=\"2\"><strong _hk=2000>ready</strong></template><script>($R[5]=(resolver, data) => {\n resolver.s(data);\n resolver.p.s = 1;\n resolver.p.v = data;\n})($R[1],\"ready\");$df(\"2\");function $df(e,n,o,t){if(!(n=document.getElementById(e)))return 0;if(!(o=document.getElementById(\"pl-\"+e)))return(_$HY.dq=_$HY.dq||{})[e]=1,0;for(;o&&(8!==o.nodeType||o.nodeValue!==\"pl-\"+e);)t=o.nextSibling,o.remove(),o=t;_$HY.done?o.remove():o.replaceWith(n.content),n.remove(),_$HY.fe(e),$dfd();return 1}function $dfl(e,o,n){if(!(o=document.getElementById(\"pl-\"+e)))return(_$HY.dlq=_$HY.dlq||{})[e]=1,0;if(o._$fl)return 1;for(n=o.nextSibling;n;){if(8===n.nodeType&&n.nodeValue===\"pl-\"+e){o.parentNode&&o.parentNode.insertBefore(o.content.cloneNode(!0),n),o._$fl=1,$dfd();return 1}n=n.nextSibling}return 0}function $dflj(e,i){for(i=0;i<e.length;i++)$dfl(e[i])}function $dfd(e,i){if(e=_$HY.dq){_$HY.dq=0;for(i in e)$df(i)}if(e=_$HY.dlq){_$HY.dlq=0;for(i in e)$dfl(i)}}function $dfs(e,c,d){(_$HY.sc=_$HY.sc||{})[e]=c,d&&((_$HY.sd=_$HY.sd||{})[e]=1)}function $dfg(e,g,i,k){if(!(g=_$HY.sg&&_$HY.sg[e]))return;for(i=0;i<g.length;i++)if(_$HY.sc&&_$HY.sc[g[i]]>0)return;for(i=0;i<g.length;i++)k=g[i],delete _$HY.sg[k],$df(k)}function $dfc(e){if(--_$HY.sc[e]<=0){delete _$HY.sc[e],_$HY.sg&&_$HY.sg[e]?$dfg(e):!(_$HY.sd&&_$HY.sd[e])&&$df(e);_$HY.sd&&delete _$HY.sd[e]}}function $dfj(e,i,n){for(i=0;i<e.length;i++)if(_$HY.sc&&_$HY.sc[e[i]]>0){for(n=0;n<e.length;n++)(_$HY.sg=_$HY.sg||{})[e[n]]=e;return}for(i=0;i<e.length;i++)$df(e[i])};$R[5]($R[4],!0);</script>"
5+
}

packages/solid-web/test/harness/scenarios.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,33 @@ function FalsyAndProp() {
371371
return <ValueCard value={val() && val()!.toUpperCase()} />;
372372
}
373373

374+
// ---------------------------------------------------------------------------
375+
// Streamed boundary whose FALLBACK contains dynamic text holes (#2877,
376+
// dom-expressions#542). The fallback serializes with <!--!$--> separator
377+
// comments inside the swap range, so $df's removal walk must stop only at its
378+
// own matching <!--pl-X--> end marker — a scan that halts at the first comment
379+
// of any kind deletes just the pre-separator slice and leaves the rest of the
380+
// fallback in the DOM as permanent debris ("ready42% done").
381+
let refreshDynamicFallback!: () => void;
382+
function DynamicFallbackStream() {
383+
const [version, setVersion] = createSignal(0);
384+
refreshDynamicFallback = () => setVersion(v => v + 1);
385+
const [progress] = createSignal(42);
386+
const report = createMemo(async () => {
387+
const v = version();
388+
await sleep(10);
389+
return v ? `ready-${v}` : "ready";
390+
});
391+
return (
392+
<div>
393+
<span>status </span>
394+
<Loading fallback={<>preparing {progress()}% done</>}>
395+
<strong>{report()}</strong>
396+
</Loading>
397+
</div>
398+
);
399+
}
400+
374401
// ---------------------------------------------------------------------------
375402
// Portal: client-only island (#2876). Server renders nothing for the portal;
376403
// the client renders its children fresh once hydration settles. Mounting into
@@ -629,6 +656,16 @@ export const scenarios: Scenario[] = [
629656
expectedTextAfterUpdate: "set:HI",
630657
stableSelector: "div"
631658
},
659+
{
660+
name: "dynamic-fallback-stream",
661+
App: DynamicFallbackStream,
662+
async: true,
663+
expectedText: "status ready",
664+
serverText: "status preparing 42% done",
665+
update: () => refreshDynamicFallback(),
666+
expectedTextAfterUpdate: "status ready-1",
667+
stableSelector: "div, span"
668+
},
632669
{
633670
name: "portal-client-island",
634671
App: PortalClientIsland,

0 commit comments

Comments
 (0)