also looking at this
fix(ssr): respect client signal ownership before evaluation
#2100
2 files
+46
-29
| @@ -27,7 +27,7 @@ | ||
| 27 | 27 | * probed already preserved correctly. These tests therefore pin the stamp's own |
| 28 | 28 | * contract, which is what the next steps will build on. |
| 29 | 29 | */ |
| 30 | import { afterAll, beforeAll, describe, expect, it } from 'bun:test' | |
| 30 | import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test' | |
| 31 | 31 | import fs from 'node:fs' |
| 32 | 32 | import os from 'node:os' |
| 33 | 33 | import path from 'node:path' |
| @@ -54,6 +54,10 @@ const label = derived(() => 'live') | ||
| 54 | 54 | fs.writeFileSync(path.join(dir, 'partials', 'plain.stx'), '<span></span>') |
| 55 | 55 | fs.mkdirSync(path.join(dir, 'components'), { recursive: true }) |
| 56 | 56 | fs.writeFileSync(path.join(dir, 'components', 'Alert.stx'), '<div class="alert"><slot /></div>') |
| 57 | fs.writeFileSync(path.join(dir, 'components', 'PostEditor.stx'), `<script client> | |
| 58 | const post = useReactiveProp('post', null) | |
| 59 | </script> | |
| 60 | <button>{{ post() ? 'Save changes' : 'Create post' }}</button>`) | |
| 57 | 61 | }) |
| 58 | 62 | |
| 59 | 63 | afterAll(() => { |
| @@ -136,6 +140,25 @@ const n = state(0) | ||
| 136 | 140 | * "untested". These are those tests. |
| 137 | 141 | */ |
| 138 | 142 | describe('cases the per-caller overrides used to carry', () => { |
| 143 | it.each([ | |
| 144 | `<script client>const post = state(null)</script><button>Save changes</button>`, | |
| 145 | '<PostEditor />', | |
| 146 | ])('never invokes a server helper shadowed by an authored client signal', async (template) => { | |
| 147 | // Stacks auto-imports its POST helper as `post`. Evaluating the client | |
| 148 | // editor's signal before preserving it used to submit an undefined URL. | |
| 149 | const post = mock(() => Promise.resolve({ server: true })) | |
| 150 | const out = await processDirectives(template, { post }, path.join(dir, 'page.stx'), options(), new Set<string>()) | |
| 151 | expect(post).not.toHaveBeenCalled() | |
| 152 | expect(out).toContain("Save changes") | |
| 153 | }) | |
| 154 | ||
| 155 | it('still evaluates a server helper when no client signal owns its name', async () => { | |
| 156 | const title = mock(() => 'Server title') | |
| 157 | const out = await processDirectives('<h1></h1>', { title }, path.join(dir, 'page.stx'), options(), new Set<string>()) | |
| 158 | expect(out).toContain('<h1>Server title</h1>') | |
| 159 | expect(title).toHaveBeenCalledTimes(1) | |
| 160 | }) | |
| 161 | ||
| 139 | 162 | it('preserves slot content authored by the CALLER', async () => { |
| 140 | 163 | // options.__stx_force_signals existed for this: `<Alert></Alert>` |
| 141 | 164 | // is written by the caller, so judging it by the COMPONENT's own script |