@@ -0,0 +1,56 @@ 1 + import { afterAll , beforeAll , describe , expect , it } from 'bun:test' 2 + import { processDirectives } from '../../src/process' 3 + import { cleanupTestDirs , createPartialFile , PARTIALS_DIR , setupTestDirs } from '../utils' 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + describe ( 'stx#1758: in @include partials with their own signals' , ( ) => { 22 + beforeAll ( setupTestDirs ) 23 + afterAll ( cleanupTestDirs ) 24 + 25 + const opts = { debug : false , partialsDir : PARTIALS_DIR , componentsDir : PARTIALS_DIR } as any 26 + const render = ( tmpl : string ) : Promise < string > => 27 + processDirectives ( tmpl , { } , 'page.stx' , opts , new Set ( ) ) 28 + 29 + it ( 'preserves when the partial defines its own signals' , async ( ) => { 30 + await createPartialFile ( 31 + 'signal-counter.stx' , 32 + `<script client>\n const n = derived(() => 42)\n</script>\n<p class="counter"></p>` , 33 + ) 34 + const out = await render ( `<div>
/var/www/reviewos-reviewos/releases/3e057f9/resources/views/layouts/app.stx:3879:20117: include error: Error loading include file signal-counter: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/signal-counter.stx'
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m [90m(line 3879, col 20117)[0m
[33m[1mMessage:[0m Error loading include file signal-counter: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/signal-counter.stx'
[34m[1mContext:[0m
[2m 3877: packages/stx/src/includes.ts modified +12 -3 Changes to packages/stx/src/includes.ts ⋯ 68 lines Show the 68 lines above this hunk @@ -69,7 +69,7 @@69 69 import type { StxOptions } from './types' 70 70 import path from 'node:path' 71 71 import { processConditionals } from './conditionals' 72 - import { processExpressions } from './expressions' 72 + import { processExpressions , usesSignalsInScript } from './expressions' 73 73 import { LRUCache } from './performance-utils' 74 74 import { createSafeFunction , isExpressionSafe , safeEvaluate , safeEvaluateObject } from './safe-evaluator' 75 75 import { transformStoreImports } from './store-imports' ⋯ 1,027 lines Show the 1,027 lines above this hunk @@ -1103,8 +1103,17 @@ catch (e) { 1103 1103 1104 1104 processedContent = processConditionals ( processedContent , includeContext , includeFilePath ) 1105 1105 1106 - 1107 - processedContent = processExpressions ( processedContent , includeContext , includeFilePath ) 1106 + 1107 + 1108 + 1109 + 1110 + 1111 + 1112 + 1113 + 1114 + 1115 + const partialHasSignals = usesSignalsInScript ( partialContent ) 1116 + processedContent = processExpressions ( processedContent , includeContext , includeFilePath , { forceSignals : partialHasSignals } ) 1108 1117 1109 1118 1110 1119 if ( preservedStyle ) {
3878:
> 3879: packages/stx/test/includes/signal-partial-mustache.test.ts added +56 -0 Changes to packages/stx/test/includes/signal-partial-mustache.test.ts @@ -0,0 +1,56 @@ 1 + import { afterAll , beforeAll , describe , expect , it } from 'bun:test' 2 + import { processDirectives } from '../../src/process' 3 + import { cleanupTestDirs , createPartialFile , PARTIALS_DIR , setupTestDirs } from '../utils' 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + describe ( 'stx#1758: in @include partials with their own signals' , ( ) => { 22 + beforeAll ( setupTestDirs ) 23 + afterAll ( cleanupTestDirs ) 24 + 25 + const opts = { debug : false , partialsDir : PARTIALS_DIR , componentsDir : PARTIALS_DIR } as any 26 + const render = ( tmpl : string ) : Promise < string > => 27 + processDirectives ( tmpl , { } , 'page.stx' , opts , new Set ( ) ) 28 + 29 + it ( 'preserves when the partial defines its own signals' , async ( ) => { 30 + await createPartialFile ( 31 + 'signal-counter.stx' , 32 + `<script client>\n const n = derived(() => 42)\n</script>\n<p class="counter"></p>` , 33 + ) 34 + const out = await render ( `<div>
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m
[33m[1mMessage:[0m Error loading include file signal-counter: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/signal-counter.stx'
</div>` ) 35 + 36 + expect ( out ) . toMatch ( / \ { \ { \ s * n \ ( \ ) \ s * \ } \ } / ) 37 + } ) 38 + 39 + it ( 'still evaluates <script server> vars in a partial (no over-preservation)' , async ( ) => { 40 + await createPartialFile ( 41 + 'server-card.stx' , 42 + `<script server>\n const who = 'Alice'\n</script>\n<p class="server"></p>` , 43 + ) 44 + const out = await render ( `<div>
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m
[33m[1mMessage:[0m Error loading include file server-card: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/server-card.stx'
</div>` ) 45 + expect ( out ) . toContain ( 'Alice' ) 46 + expect ( out ) . not . toMatch ( / \ { \ { \ s * who \ s * \ } \ } / ) 47 + } ) 48 + 49 + it ( 'does not over-preserve in a signal-free partial' , async ( ) => { 50 + await createPartialFile ( 'dumb.stx' , `<p class="dumb"></p>` ) 51 + const out = await render ( `<div>
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m
[33m[1mMessage:[0m Error loading include file dumb: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/dumb.stx'
</div>` ) 52 + 53 + 54 + expect ( out ) . not . toMatch ( / \ { \ { \ s * mystery \ s * \ } \ } / ) 55 + } ) 56 + } )
3880:
3881: [0m
</div>` ) 35 + 36 + expect ( out ) . toMatch ( / \ { \ { \ s * n \ ( \ ) \ s * \ } \ } / ) 37 + } ) 38 + 39 + it ( 'still evaluates <script server> vars in a partial (no over-preservation)' , async ( ) => { 40 + await createPartialFile ( 41 + 'server-card.stx' , 42 + `<script server>\n const who = 'Alice'\n</script>\n<p class="server"></p>` , 43 + ) 44 + const out = await render ( `<div>
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m
[33m[1mMessage:[0m Error loading include file server-card: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/server-card.stx'
</div>` ) 45 + expect ( out ) . toContain ( 'Alice' ) 46 + expect ( out ) . not . toMatch ( / \ { \ { \ s * who \ s * \ } \ } / ) 47 + } ) 48 + 49 + it ( 'does not over-preserve in a signal-free partial' , async ( ) => { 50 + await createPartialFile ( 'dumb.stx' , `<p class="dumb"></p>` ) 51 + const out = await render ( `<div>
[1m[31m╭──────────────────────────────────────────────────────────────╮[0m
[1m[31m│[0m [1m[41m ERROR [0m [31mInclude Error[0m [1m[31m│[0m
[1m[31m╰──────────────────────────────────────────────────────────────╯[0m
[36m[1mFile:[0m [2mapp.stx[0m
[33m[1mMessage:[0m Error loading include file dumb: ENOENT: no such file or directory, open '/var/www/reviewos-reviewos/releases/3e057f9/resources/views/partials/dumb.stx'
</div>` ) 52 + 53 + 54 + expect ( out ) . not . toMatch ( / \ { \ { \ s * mystery \ s * \ } \ } / ) 55 + } ) 56 + } )