@pyreon/compiler 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1720,3 +1720,937 @@ describe('JSX transform — SSR mode', () => {
1720
1720
  expect(out).toContain('_tpl(')
1721
1721
  })
1722
1722
  })
1723
+
1724
+ // ─── Signal auto-call in JSX ────────────────────────────────────────────────
1725
+
1726
+ describe('JSX transform — signal auto-call', () => {
1727
+ test('bare signal in text child is auto-called', () => {
1728
+ const result = t('function C() { const name = signal("Vít"); return <div>{name}</div> }')
1729
+ expect(result).toContain('name()')
1730
+ expect(result).toContain('_bind')
1731
+ })
1732
+
1733
+ test('signal in attribute expression is auto-called', () => {
1734
+ const result = t('function C() { const show = signal(false); return <div class={show ? "active" : ""}></div> }')
1735
+ expect(result).toContain('show()')
1736
+ expect(result).toContain('_bind')
1737
+ })
1738
+
1739
+ test('signal already called is NOT double-called', () => {
1740
+ const result = t('function C() { const count = signal(0); return <div>{count()}</div> }')
1741
+ expect(result).not.toContain('count()()')
1742
+ expect(result).toContain('count')
1743
+ })
1744
+
1745
+ test('signal in ternary is auto-called', () => {
1746
+ const result = t('function C() { const show = signal(false); return <div>{show ? "yes" : "no"}</div> }')
1747
+ expect(result).toContain('show()')
1748
+ expect(result).toContain('? "yes" : "no"')
1749
+ })
1750
+
1751
+ test('signal in template literal is auto-called', () => {
1752
+ const result = t('function C() { const name = signal("world"); return <div>{`hello ${name}`}</div> }')
1753
+ expect(result).toContain('name()')
1754
+ })
1755
+
1756
+ test('signal in component prop is auto-called with _rp', () => {
1757
+ const result = t('function C() { const val = signal(42); return <MyComp value={val} /> }')
1758
+ expect(result).toContain('_rp(() => val())')
1759
+ })
1760
+
1761
+ test('multiple signals in one expression are all auto-called', () => {
1762
+ const result = t('function C() { const a = signal(1); const b = signal(2); return <div>{a + b}</div> }')
1763
+ expect(result).toContain('a()')
1764
+ expect(result).toContain('b()')
1765
+ })
1766
+
1767
+ test('signal in conditional attribute is auto-called', () => {
1768
+ const result = t('function C() { const active = signal(false); return <div title={active ? "on" : "off"}></div> }')
1769
+ expect(result).toContain('active()')
1770
+ })
1771
+
1772
+ test('non-signal const is NOT auto-called', () => {
1773
+ const result = t('function C() { const x = 42; return <div>{x}</div> }')
1774
+ expect(result).not.toContain('x()')
1775
+ })
1776
+
1777
+ test('computed() IS auto-called (same callable pattern as signal)', () => {
1778
+ const result = t('function C() { const doubled = computed(() => 2); return <div>{doubled}</div> }')
1779
+ expect(result).toContain('doubled()')
1780
+ expect(result).toContain('_bind')
1781
+ })
1782
+
1783
+ test('computed already called is NOT double-called', () => {
1784
+ const result = t('function C() { const doubled = computed(() => 2); return <div>{doubled()}</div> }')
1785
+ expect(result).not.toContain('doubled()()')
1786
+ })
1787
+
1788
+ test('signal + computed in same expression both auto-called', () => {
1789
+ const result = t('function C() { const count = signal(0); const doubled = computed(() => count() * 2); return <div>{count} + {doubled}</div> }')
1790
+ expect(result).toContain('.data = count()')
1791
+ expect(result).toContain('.data = doubled()')
1792
+ })
1793
+
1794
+ test('signal in arrow function child is NOT auto-called (already reactive)', () => {
1795
+ const result = t('function C() { const count = signal(0); return <div>{() => count()}</div> }')
1796
+ // The arrow function is already reactive — no auto-call on the inner count
1797
+ expect(result).not.toContain('count()()')
1798
+ })
1799
+
1800
+ test('signal used in non-JSX context is NOT modified', () => {
1801
+ const result = t('function C() { const x = signal(0); console.log(x); return <div>{x}</div> }')
1802
+ // console.log(x) should keep bare x, only JSX usage gets auto-called
1803
+ expect(result).toContain('console.log(x)')
1804
+ // But JSX usage gets auto-called
1805
+ expect(result).toContain('.data = x()')
1806
+ })
1807
+
1808
+ test('signal as event handler value IS auto-called (unwraps to the handler fn)', () => {
1809
+ const result = t('function C() { const handler = signal(() => {}); return <div onClick={handler}></div> }')
1810
+ // onClick={handler} where handler is a signal → handler() unwraps to the function
1811
+ // This is correct — the event listener gets the unwrapped function value
1812
+ expect(result).toContain('handler()')
1813
+ })
1814
+
1815
+ test('module-scope signal IS tracked and auto-called', () => {
1816
+ const result = t('const globalSig = signal(0); function C() { return <div>{globalSig}</div> }')
1817
+ // Module-scope signal declarations are tracked by the single-pass walk
1818
+ expect(result).toContain('globalSig()')
1819
+ })
1820
+
1821
+ test('knownSignals option enables cross-module auto-call', () => {
1822
+ const code = 'import { count } from "./store"; function App() { return <div>{count}</div> }'
1823
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['count'] }).code
1824
+ expect(result).toContain('count()')
1825
+ expect(result).toContain('_bind')
1826
+ })
1827
+
1828
+ test('knownSignals with alias — local name is used', () => {
1829
+ const code = 'import { count as c } from "./store"; function App() { return <div>{c}</div> }'
1830
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['c'] }).code
1831
+ expect(result).toContain('c()')
1832
+ })
1833
+
1834
+ test('knownSignals does not double-call already-called signals', () => {
1835
+ const code = 'import { count } from "./store"; function App() { return <div>{count()}</div> }'
1836
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['count'] }).code
1837
+ expect(result).not.toContain('count()()')
1838
+ })
1839
+
1840
+ test('knownSignals respects scope shadowing', () => {
1841
+ const code = 'import { count } from "./store"; function App() { const count = "shadow"; return <div>{count}</div> }'
1842
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['count'] }).code
1843
+ expect(result).not.toContain('.data = count()')
1844
+ })
1845
+
1846
+ test('props.x is still inlined alongside signal auto-call', () => {
1847
+ const result = t('function C(props) { const show = signal(false); const label = props.label; return <div class={show ? label : "default"}></div> }')
1848
+ expect(result).toContain('show()')
1849
+ expect(result).toContain('props.label')
1850
+ })
1851
+
1852
+ test('shadowed signal variable by const is NOT auto-called', () => {
1853
+ const result = t(`
1854
+ function App() {
1855
+ const show = signal(false)
1856
+ function Inner() {
1857
+ const show = 'not a signal'
1858
+ return <div>{show}</div>
1859
+ }
1860
+ return <div>{show}</div>
1861
+ }
1862
+ `)
1863
+ // Inner's show is a plain string, NOT a signal — should NOT be auto-called
1864
+ // But App's show IS a signal — should be auto-called
1865
+ expect(result).toContain('show()') // App's usage
1866
+ expect(result).toContain('textContent = show') // Inner's usage (static)
1867
+ })
1868
+
1869
+ test('function parameter shadowing signal is NOT auto-called', () => {
1870
+ const result = t(`
1871
+ function App() {
1872
+ const count = signal(0)
1873
+ function Display(count) {
1874
+ return <div>{count}</div>
1875
+ }
1876
+ return <div>{count}</div>
1877
+ }
1878
+ `)
1879
+ // Display's count is a parameter, not the signal
1880
+ expect(result).toContain('textContent = count') // Display: static
1881
+ expect(result).toContain('.data = count()') // App: auto-called
1882
+ })
1883
+
1884
+ test('destructured parameter shadowing signal is NOT auto-called', () => {
1885
+ const result = t(`
1886
+ function App() {
1887
+ const name = signal('Vít')
1888
+ function Greet({ name }) {
1889
+ return <div>{name}</div>
1890
+ }
1891
+ return <div>{name}</div>
1892
+ }
1893
+ `)
1894
+ // Greet's name is destructured from props — shadows the signal
1895
+ expect(result).toContain('textContent = name') // Greet: static
1896
+ expect(result).toContain('.data = name()') // App: auto-called
1897
+ })
1898
+
1899
+ test('signal in outer scope is auto-called when NOT shadowed', () => {
1900
+ const result = t(`
1901
+ function App() {
1902
+ const name = signal('Vít')
1903
+ function Inner() {
1904
+ return <div>{name}</div>
1905
+ }
1906
+ return <div>{name}</div>
1907
+ }
1908
+ `)
1909
+ // name is NOT shadowed in Inner — auto-called in both
1910
+ const autoCallCount = (result.match(/name\(\)/g) || []).length
1911
+ expect(autoCallCount).toBeGreaterThanOrEqual(2)
1912
+ })
1913
+
1914
+ test('array destructured parameter shadowing signal is NOT auto-called', () => {
1915
+ const result = t(`
1916
+ function App() {
1917
+ const item = signal('x')
1918
+ function Inner([item]) {
1919
+ return <div>{item}</div>
1920
+ }
1921
+ return <div>{item}</div>
1922
+ }
1923
+ `)
1924
+ // Inner's item is array-destructured — shadows the signal
1925
+ expect(result).toContain('textContent = item') // Inner: static
1926
+ expect(result).toContain('.data = item()') // App: auto-called
1927
+ })
1928
+
1929
+ test('signal re-declared as signal in inner scope is still auto-called', () => {
1930
+ const result = t(`
1931
+ function App() {
1932
+ const count = signal(0)
1933
+ function Inner() {
1934
+ const count = signal(10)
1935
+ return <div>{count}</div>
1936
+ }
1937
+ return <div>{count}</div>
1938
+ }
1939
+ `)
1940
+ // Both are signal() calls — both should be auto-called
1941
+ const autoCallCount = (result.match(/count\(\)/g) || []).length
1942
+ expect(autoCallCount).toBeGreaterThanOrEqual(2)
1943
+ })
1944
+
1945
+ test('signal shadowing does not leak across sibling functions', () => {
1946
+ const result = t(`
1947
+ function App() {
1948
+ const show = signal(false)
1949
+ function A() {
1950
+ const show = 'text'
1951
+ return <div>{show}</div>
1952
+ }
1953
+ function B() {
1954
+ return <div>{show}</div>
1955
+ }
1956
+ return <div>{show}</div>
1957
+ }
1958
+ `)
1959
+ // A shadows show — static
1960
+ expect(result).toContain('textContent = show')
1961
+ // B does NOT shadow — auto-called
1962
+ // App does NOT shadow — auto-called
1963
+ const autoCallCount = (result.match(/show\(\)/g) || []).length
1964
+ expect(autoCallCount).toBeGreaterThanOrEqual(2)
1965
+ })
1966
+
1967
+ test('signal in deeply nested expression is auto-called', () => {
1968
+ const result = t('function C() { const x = signal(1); return <div>{x + x + x}</div> }')
1969
+ // All three references should be auto-called
1970
+ const autoCallCount = (result.match(/x\(\)/g) || []).length
1971
+ expect(autoCallCount).toBe(3)
1972
+ })
1973
+
1974
+ test('signal in object property value (not key) is auto-called', () => {
1975
+ const result = t('function C() { const x = signal(1); return <MyComp data={{ value: x }} /> }')
1976
+ expect(result).toContain('x()')
1977
+ expect(result).toContain('_rp(')
1978
+ })
1979
+
1980
+ test('signal + prop-derived in same expression both resolved', () => {
1981
+ const result = t('function C(props) { const x = signal(0); const label = props.label; return <div>{x ? label : "none"}</div> }')
1982
+ expect(result).toContain('x()')
1983
+ expect(result).toContain('props.label')
1984
+ expect(result).toContain('_bind')
1985
+ })
1986
+
1987
+ test('signal with no init (const x = signal()) tracked', () => {
1988
+ const result = t('function C() { const x = signal(); return <div>{x}</div> }')
1989
+ expect(result).toContain('x()')
1990
+ })
1991
+
1992
+ test('signal in member expression property position is NOT auto-called', () => {
1993
+ const result = t('function C() { const x = signal(0); return <div>{obj.x}</div> }')
1994
+ // x as a property name is not a signal reference
1995
+ expect(result).not.toContain('obj.x()')
1996
+ })
1997
+
1998
+ test('signal as member expression object IS auto-called', () => {
1999
+ const result = t('function C() { const x = signal({ a: 1 }); return <div>{x.a}</div> }')
2000
+ // x is the object, should be auto-called
2001
+ expect(result).toContain('x().a')
2002
+ })
2003
+
2004
+ test('const declared without init is not tracked as signal', () => {
2005
+ const result = t('function C() { const x = signal(0); function Inner() { let x; return <div>{x}</div> } return <div>{x}</div> }')
2006
+ // Inner's x is let, not tracked. App's x is signal
2007
+ expect(result).toContain('.data = x()')
2008
+ })
2009
+
2010
+ test('signal shadowed by let declaration in inner scope', () => {
2011
+ const result = t(`
2012
+ function C() {
2013
+ const show = signal(false)
2014
+ function Inner() {
2015
+ let show = true
2016
+ return <div>{show}</div>
2017
+ }
2018
+ return <div>{show}</div>
2019
+ }
2020
+ `)
2021
+ // Inner's let show shadows the signal — but let is not tracked by the declarator check
2022
+ // (let is not const so it's not in signalVars, but it's also not tracked as shadow)
2023
+ // Actually findShadowingNames only checks top-level VariableDeclaration declarations
2024
+ // let is VariableDeclaration kind=let — it should shadow
2025
+ expect(result).toContain('.data = show()') // outer: auto-called
2026
+ })
2027
+
2028
+ test('knownSignals with empty array does not crash', () => {
2029
+ const code = 'function App() { return <div>hello</div> }'
2030
+ const result = transformJSX(code, 'test.tsx', { knownSignals: [] }).code
2031
+ expect(result).toContain('hello')
2032
+ })
2033
+
2034
+ test('knownSignals combined with local signal declarations', () => {
2035
+ const code = 'import { count } from "./store"; function App() { const local = signal(0); return <div>{count}{local}</div> }'
2036
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['count'] }).code
2037
+ // Both the imported signal (via knownSignals) and the local signal should be auto-called
2038
+ expect(result).toContain('count()')
2039
+ expect(result).toContain('local()')
2040
+ })
2041
+
2042
+ test('knownSignals with default import name', () => {
2043
+ // When a default import resolves to a signal, the local name should be auto-called
2044
+ const code = 'import count from "./store"; function App() { return <div>{count}</div> }'
2045
+ const result = transformJSX(code, 'test.tsx', { knownSignals: ['count'] }).code
2046
+ expect(result).toContain('count()')
2047
+ expect(result).toContain('_bind')
2048
+ })
2049
+ })
2050
+
2051
+ // ─── Additional branch coverage for >= 90% ─────────────────────────────────
2052
+
2053
+ describe('JSX transform — template reactive style _bindDirect path', () => {
2054
+ test('reactive style accessor uses _bindDirect with cssText updater', () => {
2055
+ const result = t('<div style={getStyle()}><span /></div>')
2056
+ expect(result).toContain('_bindDirect(getStyle,')
2057
+ expect(result).toContain('style.cssText')
2058
+ })
2059
+
2060
+ test('reactive style accessor with object check in updater', () => {
2061
+ const result = t('<div style={styleSignal()}><span /></div>')
2062
+ expect(result).toContain('_bindDirect(styleSignal,')
2063
+ // The updater should handle both string and object
2064
+ expect(result).toContain('typeof v === "string"')
2065
+ expect(result).toContain('Object.assign')
2066
+ })
2067
+ })
2068
+
2069
+ describe('JSX transform — template combined _bind for complex expressions', () => {
2070
+ test('complex attribute expression uses combined _bind', () => {
2071
+ const result = t('<div class={`${a()} ${b()}`}><span /></div>')
2072
+ expect(result).toContain('_bind(() => {')
2073
+ expect(result).toContain('className')
2074
+ })
2075
+
2076
+ test('dynamic spread in template uses _applyProps in reactive _bind', () => {
2077
+ const result = t('<div {...getProps()}><span /></div>')
2078
+ expect(result).toContain('_tpl(')
2079
+ expect(result).toContain('_applyProps')
2080
+ expect(result).toContain('_bind')
2081
+ })
2082
+ })
2083
+
2084
+ describe('JSX transform — children expression as bareIdentifier "children"', () => {
2085
+ test('bare children identifier uses _mountSlot', () => {
2086
+ const result = t('function C(props) { const children = props.children; return <div>{children}</div> }')
2087
+ expect(result).toContain('_mountSlot')
2088
+ })
2089
+ })
2090
+
2091
+ describe('JSX transform — template static expression string attr via JSX expression', () => {
2092
+ test('numeric expression attribute baked into HTML', () => {
2093
+ const result = t('<div tabindex={3}><span /></div>')
2094
+ expect(result).toContain('tabindex=\\"3\\"')
2095
+ })
2096
+
2097
+ test('boolean false expression attribute omitted from HTML', () => {
2098
+ const result = t('<div hidden={false}><span /></div>')
2099
+ expect(result).not.toContain('hidden')
2100
+ })
2101
+
2102
+ test('null expression attribute omitted from HTML', () => {
2103
+ const result = t('<div hidden={null}><span /></div>')
2104
+ expect(result).not.toContain('hidden')
2105
+ })
2106
+ })
2107
+
2108
+ describe('JSX transform — isPureStaticCall edge cases', () => {
2109
+ test('pure call with spread argument IS wrapped (not pure)', () => {
2110
+ const result = t('<div>{Math.max(...nums)}</div>')
2111
+ expect(result).toContain('.data =')
2112
+ })
2113
+
2114
+ test('Array.isArray with static arg is not wrapped', () => {
2115
+ const result = t('<div>{Array.isArray(null)}</div>')
2116
+ expect(result).not.toContain('() =>')
2117
+ })
2118
+
2119
+ test('encodeURIComponent with static string is not wrapped', () => {
2120
+ const result = t('<div>{encodeURIComponent("hello world")}</div>')
2121
+ expect(result).not.toContain('() =>')
2122
+ })
2123
+
2124
+ test('Date.now is not wrapped (no args)', () => {
2125
+ const result = t('<div>{Date.now()}</div>')
2126
+ expect(result).not.toContain('() =>')
2127
+ })
2128
+
2129
+ test('standalone parseInt with static arg is not wrapped', () => {
2130
+ const result = t('<div>{parseInt("42")}</div>')
2131
+ expect(result).not.toContain('() =>')
2132
+ })
2133
+ })
2134
+
2135
+ describe('JSX transform — isStatic edge cases', () => {
2136
+ test('template literal with no substitutions is static', () => {
2137
+ const result = t('<div>{`plain text`}</div>')
2138
+ expect(result).not.toContain('_bind')
2139
+ })
2140
+
2141
+ test('template literal with substitution is dynamic', () => {
2142
+ const result = t('<div>{`${x()}`}</div>')
2143
+ expect(result).toContain('_bind')
2144
+ })
2145
+ })
2146
+
2147
+ describe('JSX transform — signal auto-call in template _bind expressions', () => {
2148
+ test('signal in _bind reactive attribute expression', () => {
2149
+ const result = t('function C() { const cls = signal("a"); return <div class={`${cls} extra`}><span /></div> }')
2150
+ expect(result).toContain('cls()')
2151
+ expect(result).toContain('_bind')
2152
+ })
2153
+
2154
+ test('signal in template text child expression', () => {
2155
+ const result = t('function C() { const name = signal("X"); return <div>{`Hello ${name}`}</div> }')
2156
+ expect(result).toContain('name()')
2157
+ })
2158
+
2159
+ test('signal auto-call with addition', () => {
2160
+ const result = t('function C() { const a = signal(1); const b = signal(2); return <div>{a + b}</div> }')
2161
+ expect(result).toContain('a()')
2162
+ expect(result).toContain('b()')
2163
+ })
2164
+ })
2165
+
2166
+ describe('JSX transform — walkNode edge cases for scope cleanup', () => {
2167
+ test('JSXExpressionContainer at top level within function', () => {
2168
+ // This exercises the walkNode JSXExpressionContainer path with scope shadows
2169
+ const result = t(`
2170
+ function App() {
2171
+ const x = signal(0)
2172
+ function Inner() {
2173
+ const x = 'plain'
2174
+ return <MyComp>{x}</MyComp>
2175
+ }
2176
+ return <div>{x}</div>
2177
+ }
2178
+ `)
2179
+ expect(result).toContain('.data = x()')
2180
+ })
2181
+
2182
+ test('template emit within scoped function with signal shadowing', () => {
2183
+ const result = t(`
2184
+ function App() {
2185
+ const count = signal(0)
2186
+ function Nested() {
2187
+ const count = 'static'
2188
+ return <div><span>{count}</span></div>
2189
+ }
2190
+ return <div><span>{count}</span></div>
2191
+ }
2192
+ `)
2193
+ // Nested: count is shadowed, static
2194
+ expect(result).toContain('textContent = count')
2195
+ // App: count is signal, auto-called
2196
+ expect(result).toContain('.data = count()')
2197
+ })
2198
+ })
2199
+
2200
+ describe('JSX transform — parse error handling', () => {
2201
+ test('returns original code on parse error', () => {
2202
+ const result = transformJSX('this is not {valid js <>', 'bad.tsx')
2203
+ expect(result.code).toBe('this is not {valid js <>')
2204
+ expect(result.warnings).toHaveLength(0)
2205
+ })
2206
+ })
2207
+
2208
+ describe('JSX transform — reactive combined _bind for multiple reactive attrs', () => {
2209
+ test('multiple reactive attributes on same element with complex expressions', () => {
2210
+ const result = t('<div class={`${a()} b`} title={`${c()} d`}><span /></div>')
2211
+ expect(result).toContain('_bind(() => {')
2212
+ expect(result).toContain('className')
2213
+ expect(result).toContain('setAttribute("title"')
2214
+ })
2215
+ })
2216
+
2217
+ describe('JSX transform — signalVars.size > shadowedSignals.size check', () => {
2218
+ test('when all signals are shadowed, no auto-call happens', () => {
2219
+ const result = t(`
2220
+ function App() {
2221
+ const x = signal(0)
2222
+ function Inner() {
2223
+ const x = 'plain'
2224
+ return <div class={x + " extra"}></div>
2225
+ }
2226
+ return <div>{x}</div>
2227
+ }
2228
+ `)
2229
+ // Inner's x is NOT auto-called
2230
+ expect(result).toContain('className = x + " extra"')
2231
+ // App's x IS auto-called
2232
+ expect(result).toContain('.data = x()')
2233
+ })
2234
+ })
2235
+
2236
+ describe('JSX transform — _isDynamic with signal member expression and call position', () => {
2237
+ test('signal.set() is NOT flagged as dynamic (signal in callee position)', () => {
2238
+ // When signal is the callee of a call expression, it's already being called
2239
+ const result = t('function C() { const x = signal(0); return <button onClick={() => x.set(1)}>click</button> }')
2240
+ // onClick is an event handler — not wrapped regardless
2241
+ expect(result).not.toContain('_rp')
2242
+ })
2243
+
2244
+ test('signal in property name position of member expression is NOT dynamic', () => {
2245
+ const result = t('function C() { const x = signal(0); return <div title={obj.x}></div> }')
2246
+ // obj.x — x is property name, not signal reference
2247
+ expect(result).not.toContain('_bind')
2248
+ })
2249
+ })
2250
+
2251
+ // ─── Branch coverage: referencesPropDerived with computed MemberExpression ──
2252
+
2253
+ describe('JSX transform — referencesPropDerived computed access', () => {
2254
+ test('prop-derived var used as computed property key is treated as reference', () => {
2255
+ const result = t('function C(props) { const key = props.key; return <div title={obj[key]}></div> }')
2256
+ // key is used as computed property — it IS a reference (p.computed === true)
2257
+ expect(result).toContain('props.key')
2258
+ expect(result).toContain('_bind')
2259
+ })
2260
+
2261
+ test('prop-derived var in non-computed property position is NOT a reference', () => {
2262
+ const result = t('function C(props) { const data = props.data; return <div title={result.data}></div> }')
2263
+ // result.data — 'data' is a non-computed property name, NOT a prop-derived reference
2264
+ expect(result).not.toContain('_bind')
2265
+ })
2266
+ })
2267
+
2268
+ // ─── Branch coverage: template attrSetter for style (line 940) ──────────────
2269
+
2270
+ describe('JSX transform — template style attribute combined _bind', () => {
2271
+ test('complex reactive style uses cssText in combined _bind', () => {
2272
+ const result = t('<div style={getStyle() + "extra"}>text</div>')
2273
+ expect(result).toContain('style.cssText')
2274
+ expect(result).toContain('_bind(() => {')
2275
+ })
2276
+ })
2277
+
2278
+ // ─── Branch coverage: processOneAttr key attr (line 1008) ───────────────────
2279
+
2280
+ describe('JSX transform — template with key attribute on child element', () => {
2281
+ test('key attribute on child element is stripped in template', () => {
2282
+ // key on inner child doesn't bail template (only root key bails)
2283
+ // But templateElementCount bails on key attr on any element
2284
+ const result = t('<div><span key="a">text</span></div>')
2285
+ expect(result).not.toContain('_tpl(')
2286
+ })
2287
+ })
2288
+
2289
+ // ─── Branch coverage: selfClosing template bail (line 313) ──────────────────
2290
+
2291
+ describe('JSX transform — self-closing element template bail', () => {
2292
+ test('self-closing elements skip template emission', () => {
2293
+ const result = t('<div class={cls()} />')
2294
+ // Self-closing root element — tryTemplateEmit returns false
2295
+ expect(result).toContain('() => cls()')
2296
+ expect(result).not.toContain('_tpl(')
2297
+ })
2298
+ })
2299
+
2300
+ // ─── Branch coverage: isStatic types (line 1388-1389) ───────────────────────
2301
+
2302
+ describe('JSX transform — isStatic for various literal types', () => {
2303
+ test('NullLiteral is static', () => {
2304
+ const result = t('<div>{<span data-x={null} />}</div>')
2305
+ expect(result).toContain('const _$h0')
2306
+ })
2307
+
2308
+ test('template literal with expressions is not static', () => {
2309
+ const result = t('<div>{<span data-x={`${x}`} />}</div>')
2310
+ expect(result).not.toContain('const _$h0')
2311
+ })
2312
+ })
2313
+
2314
+ // ─── Branch coverage: accessesProps with arrow function inside (line 679) ────
2315
+
2316
+ describe('JSX transform — accessesProps stops at nested functions', () => {
2317
+ test('props read inside arrow function does not make outer expression reactive', () => {
2318
+ const result = t('function C(props) { return <div title={items.map(x => props.fmt(x))}></div> }')
2319
+ // The arrow function contains a props read, but accessesProps stops at arrow boundaries
2320
+ expect(result).toContain('.map')
2321
+ })
2322
+ })
2323
+
2324
+ // ─── Branch coverage: shouldWrap pure static call (line 688) ────────────────
2325
+
2326
+ describe('JSX transform — shouldWrap skips pure static calls', () => {
2327
+ test('Array.from with static arg in attribute position', () => {
2328
+ const result = t('<div data-arr={Array.from("abc")}></div>')
2329
+ expect(result).not.toContain('_bind')
2330
+ })
2331
+ })
2332
+
2333
+ // ─── Branch coverage: isChildrenExpression fallthrough (line 1321) ──────────
2334
+
2335
+ describe('JSX transform — isChildrenExpression edge cases', () => {
2336
+ test('expression ending with .children uses _mountSlot', () => {
2337
+ const result = t('function C(props) { return <div>{config.children}</div> }')
2338
+ expect(result).toContain('_mountSlot')
2339
+ })
2340
+
2341
+ test('identifier named exactly children uses _mountSlot', () => {
2342
+ const result = t('function C() { return <div>{children}</div> }')
2343
+ expect(result).toContain('_mountSlot')
2344
+ })
2345
+
2346
+ test('expression NOT ending with .children does NOT use _mountSlot', () => {
2347
+ const result = t('function C(props) { return <div>{config.items}</div> }')
2348
+ expect(result).not.toContain('_mountSlot')
2349
+ })
2350
+ })
2351
+
2352
+ // ─── Branch coverage: _isDynamic ArrowFunctionExpression stop (line 656) ────
2353
+
2354
+ describe('JSX transform — _isDynamic stops at nested arrow functions', () => {
2355
+ test('call inside arrow function does not make outer expression dynamic', () => {
2356
+ const result = t('<MyComp render={() => fn()} />')
2357
+ // Arrow function prevents _isDynamic from recursing into fn()
2358
+ expect(result).not.toContain('_rp(')
2359
+ })
2360
+ })
2361
+
2362
+ // ─── Branch coverage: tryDirectSignalRef edge cases (line 922) ──────────────
2363
+
2364
+ describe('JSX transform — tryDirectSignalRef with arguments', () => {
2365
+ test('call with arguments does NOT use _bindDirect', () => {
2366
+ const result = t('<div class={getClass("primary")}><span /></div>')
2367
+ // Has arguments — not a direct signal ref
2368
+ expect(result).not.toContain('_bindDirect')
2369
+ expect(result).toContain('_bind(() => {')
2370
+ })
2371
+ })
2372
+
2373
+ // ─── Branch coverage: unwrapAccessor for function expression (line 928) ─────
2374
+
2375
+ describe('JSX transform — unwrapAccessor with function expression', () => {
2376
+ test('function expression in attribute is called in bind', () => {
2377
+ const result = t('<div class={function() { return "cls" }}><span /></div>')
2378
+ expect(result).toContain('_bind')
2379
+ })
2380
+ })
2381
+
2382
+ // ─── Branch coverage: collectPropDerivedFromDecl callbackDepth (line 498) ───
2383
+
2384
+ describe('JSX transform — prop-derived vars inside callbacks excluded', () => {
2385
+ test('const inside .map callback is NOT tracked as prop-derived', () => {
2386
+ const result = t('function C(props) { return <div>{items.map(item => { const x = props.y; return <span>{x}</span> })}</div> }')
2387
+ // x is declared inside a callback (callbackDepth > 0) — not tracked
2388
+ expect(result).toContain('() =>')
2389
+ })
2390
+ })
2391
+
2392
+ // ─── Branch coverage: static JSX in component prop hoisting (line 359) ──────
2393
+
2394
+ describe('JSX transform — component prop static JSX hoisting', () => {
2395
+ test('single JSX element in component prop is NOT hoisted but walked', () => {
2396
+ const result = t('<MyComp icon={<span>icon</span>} />')
2397
+ // Single JSX element prop → walked (line 354-356), not hoisted
2398
+ expect(result).not.toContain('const _$h0')
2399
+ expect(result).toContain('<span>icon</span>')
2400
+ })
2401
+
2402
+ test('non-JSX static expression in component prop gets hoisted', () => {
2403
+ // This exercises the maybeHoist path (line 358-360)
2404
+ const result = t('<MyComp render={12} />')
2405
+ // Static numeric — no wrapping needed
2406
+ expect(result).not.toContain('_rp(')
2407
+ })
2408
+ })
2409
+
2410
+ // ─── Branch coverage: templateElementCount bail at non-lowercase (line 825) ──
2411
+
2412
+ describe('JSX transform — template element count bail on uppercase', () => {
2413
+ test('component element inside template bails', () => {
2414
+ const result = t('<div><Component /><span /></div>')
2415
+ expect(result).not.toContain('_tpl(')
2416
+ })
2417
+ })
2418
+
2419
+ // ─── Branch coverage: maybeRegisterComponentProps with no params (line 518) ──
2420
+
2421
+ describe('JSX transform — component with no params not tracked', () => {
2422
+ test('parameterless function not tracked as component props', () => {
2423
+ const result = t('function C() { return <div>hello</div> }')
2424
+ expect(result).toContain('_tpl(')
2425
+ expect(result).not.toContain('_bind')
2426
+ })
2427
+ })
2428
+
2429
+ // ─── Branch coverage: tpl null cleanup return (line 1156/1171/1179) ────────
2430
+
2431
+ describe('JSX transform — template processChildren null bail', () => {
2432
+ test('template bails when child element has no tag name', () => {
2433
+ // Member expression tag → empty tag name → processElement returns null
2434
+ const result = t('<div><ns.Comp><span /></ns.Comp></div>')
2435
+ expect(result).not.toContain('_tpl(')
2436
+ })
2437
+ })
2438
+
2439
+ // ─── Branch coverage: more edge cases for various ?? and ?. operators ───────
2440
+
2441
+ describe('JSX transform — additional branch coverage paths', () => {
2442
+ test('arrow function with expression body (no block statement)', () => {
2443
+ const result = t('<div class={() => cls()}><span /></div>')
2444
+ expect(result).toContain('_bindDirect(cls,')
2445
+ })
2446
+
2447
+ test('function expression with block body in attribute', () => {
2448
+ const result = t('<div class={function() { return cls() }}><span /></div>')
2449
+ expect(result).toContain('_bind')
2450
+ })
2451
+
2452
+ test('prop-derived var used inside a nested function arg but NOT as callback', () => {
2453
+ const result = t('function C(props) { const x = props.y; return <div>{x + other(x)}</div> }')
2454
+ expect(result).toContain('props.y')
2455
+ expect(result).toContain('_bind')
2456
+ })
2457
+
2458
+ test('mixed static and dynamic props on template element', () => {
2459
+ const result = t('<div class="static" title={x()} data-id={42}><span /></div>')
2460
+ expect(result).toContain('class=\\"static\\"')
2461
+ expect(result).toContain('_bindDirect(x,')
2462
+ expect(result).toContain('data-id=\\"42\\"')
2463
+ })
2464
+
2465
+ test('template with nested elements each having dynamic attributes', () => {
2466
+ const result = t('<div><span class={a()}><em title={b()}>text</em></span></div>')
2467
+ expect(result).toContain('_tpl(')
2468
+ expect(result).toContain('_bindDirect(a,')
2469
+ expect(result).toContain('_bindDirect(b,')
2470
+ })
2471
+
2472
+ test('signal auto-call works inside template _bind for text', () => {
2473
+ const result = t('function C() { const x = signal(1); return <div>{x + 1}</div> }')
2474
+ expect(result).toContain('x() + 1')
2475
+ expect(result).toContain('_bind')
2476
+ })
2477
+
2478
+ test('signal auto-call inside template attribute _bind', () => {
2479
+ const result = t('function C() { const cls = signal("a"); return <div class={cls + " b"}><span /></div> }')
2480
+ expect(result).toContain('cls() + " b"')
2481
+ expect(result).toContain('_bind')
2482
+ })
2483
+
2484
+ test('template with event + ref + dynamic attr + text child', () => {
2485
+ const result = t('<div ref={myRef} onClick={handler} class={cls()} title="static">{text()}</div>')
2486
+ expect(result).toContain('_tpl(')
2487
+ expect(result).toContain('myRef')
2488
+ expect(result).toContain('__ev_click = handler')
2489
+ expect(result).toContain('_bindDirect(cls,')
2490
+ expect(result).toContain('_bindText(text,')
2491
+ })
2492
+
2493
+ test('template with non-delegated event using addEventListener', () => {
2494
+ const result = t('<div onScroll={handler}><span /></div>')
2495
+ expect(result).toContain('addEventListener("scroll", handler)')
2496
+ expect(result).not.toContain('__ev_')
2497
+ })
2498
+
2499
+ test('forEachChild with non-array non-object values', () => {
2500
+ // Edge case: JSX text node has primitive value property
2501
+ const result = t('<div>plain text between elements<span /></div>')
2502
+ expect(result).toContain('_tpl(')
2503
+ })
2504
+
2505
+ test('self-closing void element in mixed children template', () => {
2506
+ const result = t('<div><input />{value()}</div>')
2507
+ expect(result).toContain('_tpl(')
2508
+ expect(result).toContain('childNodes[')
2509
+ expect(result).toContain('_bindText(value,')
2510
+ })
2511
+
2512
+ test('multiple signals from same component all tracked', () => {
2513
+ const result = t(`
2514
+ function C() {
2515
+ const a = signal(1)
2516
+ const b = signal(2)
2517
+ const c = signal(3)
2518
+ return <div>
2519
+ <span>{a}</span>
2520
+ <em>{b}</em>
2521
+ <strong>{c}</strong>
2522
+ </div>
2523
+ }
2524
+ `)
2525
+ expect(result).toContain('a()')
2526
+ expect(result).toContain('b()')
2527
+ expect(result).toContain('c()')
2528
+ })
2529
+
2530
+ test('signal auto-call with binary and unary expressions', () => {
2531
+ const result = t('function C() { const x = signal(5); return <div>{-x}</div> }')
2532
+ expect(result).toContain('x()')
2533
+ expect(result).toContain('_bind')
2534
+ })
2535
+
2536
+ test('signal in computed property access is auto-called', () => {
2537
+ const result = t('function C() { const idx = signal(0); return <div>{arr[idx]}</div> }')
2538
+ expect(result).toContain('idx()')
2539
+ })
2540
+
2541
+ test('signal variable reference not confused with same-name property', () => {
2542
+ const result = t('function C() { const x = signal(0); return <div data-val={obj.method(x)}></div> }')
2543
+ expect(result).toContain('x()')
2544
+ expect(result).toContain('_bind')
2545
+ })
2546
+
2547
+ test('template with static spread on root and dynamic inner attr', () => {
2548
+ const result = t('<div {...staticProps}><span class={cls()}>text</span></div>')
2549
+ expect(result).toContain('_tpl(')
2550
+ expect(result).toContain('_applyProps')
2551
+ expect(result).toContain('_bindDirect(cls,')
2552
+ })
2553
+
2554
+ test('empty JSX expression in template attribute position', () => {
2555
+ const result = t('<div class={/* comment */}><span /></div>')
2556
+ expect(result).toContain('_tpl(')
2557
+ })
2558
+
2559
+ test('ternary in template attribute without signal', () => {
2560
+ const result = t('<div class={x ? "a" : "b"}><span /></div>')
2561
+ // No calls — not dynamic
2562
+ expect(result).toContain('className = x ? "a" : "b"')
2563
+ })
2564
+
2565
+ test('variable declaration kind is let — not tracked for prop-derived', () => {
2566
+ const result = t('function C(props) { let x = props.y; return <div>{x}</div> }')
2567
+ // let is not tracked — x is static
2568
+ expect(result).toContain('textContent = x')
2569
+ })
2570
+
2571
+ test('FunctionDeclaration with JSX detected as component', () => {
2572
+ const result = t('function MyComp(props) { return <div class={props.cls}></div> }')
2573
+ expect(result).toContain('_bind')
2574
+ expect(result).toContain('props.cls')
2575
+ })
2576
+
2577
+ test('ArrowFunctionExpression with JSX and single param detected as component', () => {
2578
+ const result = t('const MyComp = (props) => <div class={props.cls}></div>')
2579
+ expect(result).toContain('_bind')
2580
+ expect(result).toContain('props.cls')
2581
+ })
2582
+
2583
+ test('signal NOT tracked inside callback arg (callbackDepth > 0)', () => {
2584
+ // collectPropDerivedFromDecl skips when callbackDepth > 0
2585
+ const result = t('function C(props) { return <div>{items.map(item => { const x = signal(0); return <span>{x}</span> })}</div> }')
2586
+ // x is inside a callback — signal tracking doesn't apply at callback depth
2587
+ expect(result).toContain('() =>')
2588
+ })
2589
+
2590
+ test('template with empty expression in attribute (attrIsDynamic false branch)', () => {
2591
+ // Empty expression in attribute: data-x={/* */} — attrIsDynamic returns false
2592
+ const result = t('<div data-x={/* comment */}><span /></div>')
2593
+ expect(result).toContain('_tpl(')
2594
+ })
2595
+
2596
+ test('template with only static attributes — elementHasDynamic false', () => {
2597
+ const result = t('<div class="a" title="b"><span class="c">text</span></div>')
2598
+ expect(result).toContain('_tpl(')
2599
+ // No _bind needed for fully static tree
2600
+ expect(result).toContain('() => null')
2601
+ })
2602
+
2603
+ test('signal auto-call with signal as callee of call expression', () => {
2604
+ // signal()(args) — signal IS the callee of a call, already being called
2605
+ const result = t('function C() { const fn = signal(() => 1); return <div>{fn()}</div> }')
2606
+ // fn() is already a call — no double call
2607
+ expect(result).not.toContain('fn()()')
2608
+ })
2609
+
2610
+ test('signal auto-call not triggered on arrow function children', () => {
2611
+ // Arrow functions in JSX are not recursed into by referencesSignalVar
2612
+ const result = t('function C() { const x = signal(0); return <div>{() => { const x = "shadow"; return x }}</div> }')
2613
+ // The arrow function is not touched
2614
+ expect(result).toBeDefined()
2615
+ })
2616
+
2617
+ test('template with deeply nested mixed expressions', () => {
2618
+ const result = t('<div><span><em>{a()}</em></span><strong>{b()}</strong></div>')
2619
+ expect(result).toContain('_tpl(')
2620
+ expect(result).toContain('_bindText(a,')
2621
+ expect(result).toContain('_bindText(b,')
2622
+ })
2623
+
2624
+ test('signal in JSX attribute expression container — auto-called in bind', () => {
2625
+ const result = t('function C() { const x = signal(0); return <div data-val={x}><span /></div> }')
2626
+ // x is a signal identifier in an attribute — should be auto-called
2627
+ expect(result).toContain('x()')
2628
+ expect(result).toContain('_bind')
2629
+ })
2630
+
2631
+ test('namespace attribute in template element', () => {
2632
+ // xml:lang or xlink:href — JSXNamespacedName, not JSXIdentifier
2633
+ const result = t('<svg><use xlink:href="#icon"><rect /></use></svg>')
2634
+ expect(result).toBeDefined()
2635
+ })
2636
+
2637
+ test('signal as only child of component uses auto-call', () => {
2638
+ const result = t('function C() { const x = signal(0); return <MyComp>{x}</MyComp> }')
2639
+ expect(result).toContain('() => x()')
2640
+ })
2641
+
2642
+ test('multiple signals with complex nesting', () => {
2643
+ const result = t(`
2644
+ function C() {
2645
+ const a = signal(1)
2646
+ const b = signal('text')
2647
+ return <div class={a ? 'active' : 'inactive'}>
2648
+ <span>{b}</span>
2649
+ <em>{a > 0 ? b : 'none'}</em>
2650
+ </div>
2651
+ }
2652
+ `)
2653
+ expect(result).toContain('a()')
2654
+ expect(result).toContain('b()')
2655
+ })
2656
+ })