@pyreon/compiler 0.13.0 → 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.
- package/README.md +14 -4
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +1113 -406
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +140 -14
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/index.ts +10 -1
- package/src/jsx.ts +839 -782
- package/src/pyreon-intercept.ts +504 -0
- package/src/test-audit.ts +435 -0
- package/src/tests/depth-stress.test.ts +16 -0
- package/src/tests/detector-tag-consistency.test.ts +83 -0
- package/src/tests/jsx.test.ts +934 -0
- package/src/tests/native-equivalence.test.ts +654 -0
- package/src/tests/project-scanner.test.ts +30 -0
- package/src/tests/pyreon-intercept.test.ts +331 -0
- package/src/tests/react-intercept.test.ts +354 -0
- package/src/tests/test-audit.test.ts +549 -0
package/src/tests/jsx.test.ts
CHANGED
|
@@ -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
|
+
})
|