@barefootjs/go-template 0.1.2 → 0.2.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.
@@ -22,14 +22,6 @@ runAdapterConformanceTests({
22
22
  // `<!--bf-cond-start:sN-->` / `<!--bf-cond-end:sN-->` comment pairs)
23
23
  // is now collapsed by `normalizeHTML` in adapter-tests (#1266).
24
24
  //
25
- // `nullish-coalescing-jsx` / `return-nullish-coalescing` have a
26
- // separate semantic divergence: the Go template's `{{if ne .Banner
27
- // ""}}` condition treats an unset `Banner` (Go nil) as `!= ""` and
28
- // takes the truthy branch with empty content, while Hono's JS
29
- // `??` operator falls through to the JSX default. That's a Go-
30
- // adapter branch-selection bug — fixing it is out of scope for
31
- // #1266.
32
- //
33
25
  // `return-map` uses a `data-key` serialisation shape that differs
34
26
  // between Hono (runtime helper) and Go (template variable) in a
35
27
  // way that isn't structural — leaving it on `skipJsx` until a
@@ -43,8 +35,6 @@ runAdapterConformanceTests({
43
35
  // `BF104` at build time instead of silently emitting invalid
44
36
  // template syntax (#1266).
45
37
  skipJsx: [
46
- 'nullish-coalescing-jsx',
47
- 'return-nullish-coalescing',
48
38
  'return-map',
49
39
  // #1297 fixed the harness-side IR emission gate (multi-component
50
40
  // sources now emit one `ir` file per component, and the harness
@@ -1034,6 +1024,116 @@ export function ItemChecker() {
1034
1024
  })
1035
1025
  })
1036
1026
 
1027
+ describe('findLast/findLastIndex - adapter specific', () => {
1028
+ test('renders findLast() with equality predicate via bf_find_last', () => {
1029
+ const result = compileAndGenerate(`
1030
+ "use client"
1031
+ import { createSignal } from "@barefootjs/client"
1032
+
1033
+ type Item = { name: string; done: boolean }
1034
+
1035
+ export function ItemChecker() {
1036
+ const [items, setItems] = createSignal<Item[]>([])
1037
+ return <div>{items().findLast(t => t.done) ? 'Found' : 'Not found'}</div>
1038
+ }
1039
+ `)
1040
+ expect(result.template).toContain('bf_find_last .Items "Done" true')
1041
+ expect(result.template).toContain('Found')
1042
+ })
1043
+
1044
+ test('renders findLast() with complex predicate via range without break', () => {
1045
+ const result = compileAndGenerate(`
1046
+ "use client"
1047
+ import { createSignal } from "@barefootjs/client"
1048
+
1049
+ type Item = { price: number; category: string }
1050
+
1051
+ export function ItemFinder() {
1052
+ const [items, setItems] = createSignal<Item[]>([])
1053
+ const [type, setType] = createSignal('')
1054
+ return <div>{items().findLast(t => t.price > 100 && t.category === type())}</div>
1055
+ }
1056
+ `)
1057
+ expect(result.template).toContain('{{range')
1058
+ expect(result.template).toContain('$bf_r')
1059
+ expect(result.template).not.toContain('{{break}}')
1060
+ })
1061
+
1062
+ test('renders findLastIndex() with equality predicate via bf_find_last_index', () => {
1063
+ const result = compileAndGenerate(`
1064
+ "use client"
1065
+ import { createSignal } from "@barefootjs/client"
1066
+
1067
+ type Item = { name: string; done: boolean }
1068
+
1069
+ export function ItemChecker() {
1070
+ const [items, setItems] = createSignal<Item[]>([])
1071
+ return <div>idx: {items().findLastIndex(t => t.done)}</div>
1072
+ }
1073
+ `)
1074
+ expect(result.template).toContain('bf_find_last_index .Items "Done" true')
1075
+ })
1076
+
1077
+ test('renders findLastIndex() with complex predicate via range', () => {
1078
+ const result = compileAndGenerate(`
1079
+ "use client"
1080
+ import { createSignal } from "@barefootjs/client"
1081
+
1082
+ type Item = { price: number; active: boolean }
1083
+
1084
+ export function ItemFinder() {
1085
+ const [items, setItems] = createSignal<Item[]>([])
1086
+ return <div>{items().findLastIndex(t => t.price > 50 && t.active)}</div>
1087
+ }
1088
+ `)
1089
+ const varMatch = result.template.match(/(\$bf_r\d+) := -1/)
1090
+ expect(varMatch).not.toBeNull()
1091
+ expect(result.template).toContain(`${varMatch![1]} = $i`)
1092
+ expect(result.template).not.toContain('{{break}}')
1093
+ })
1094
+
1095
+ test('findLast() complex predicate in IR-level ternary works via preamble splitting', () => {
1096
+ const adapter = new GoTemplateAdapter()
1097
+ const ir = compileToIR(`
1098
+ "use client"
1099
+ import { createSignal } from "@barefootjs/client"
1100
+
1101
+ type Item = { price: number; category: string }
1102
+
1103
+ export function ItemFinder() {
1104
+ const [items, setItems] = createSignal<Item[]>([])
1105
+ const [type, setType] = createSignal('')
1106
+ return <div>{items().findLast(t => t.price > 100 && t.category === type()) ? 'yes' : 'no'}</div>
1107
+ }
1108
+ `, adapter)
1109
+ const output = adapter.generate(ir)
1110
+ expect(adapter.errors.filter(e => e.code === 'BF101')).toEqual([])
1111
+ expect(output.template).toMatch(/\$bf_r\d+ := ""/)
1112
+ expect(output.template).toContain('yes')
1113
+ })
1114
+
1115
+ test('findLast() complex predicate in binary expression compiles via preamble hoisting', () => {
1116
+ const adapter = new GoTemplateAdapter()
1117
+ const ir = compileToIR(`
1118
+ "use client"
1119
+ import { createSignal } from "@barefootjs/client"
1120
+
1121
+ type Item = { price: number; category: string }
1122
+
1123
+ export function ItemFinder() {
1124
+ const [items, setItems] = createSignal<Item[]>([])
1125
+ const [type, setType] = createSignal('')
1126
+ return <div class={items().findLast(t => t.price > 100 && t.category === type()) === 'special' ? 'highlight' : 'normal'}>test</div>
1127
+ }
1128
+ `, adapter)
1129
+ const output = adapter.generate(ir)
1130
+ expect(adapter.errors.filter(e => e.code === 'BF101')).toEqual([])
1131
+ expect(output.template).toMatch(/\$bf_r\d+ := ""/)
1132
+ expect(output.template).toContain('eq')
1133
+ expect(output.template).toContain('"special"')
1134
+ })
1135
+ })
1136
+
1037
1137
  describe('component root scope comment propagation', () => {
1038
1138
  test('component root in client component outputs bfScopeComment', () => {
1039
1139
  const result = compileAndGenerate(`
@@ -1671,6 +1771,32 @@ export { A }`)
1671
1771
  expect(result.template).toContain('bf_join (bf_concat .Left .Right) " "')
1672
1772
  })
1673
1773
  })
1774
+
1775
+ describe('.entries() / .keys() / .values() iteration shapes (#1448 Tier B)', () => {
1776
+ test('.entries().map(([i, v]) => ...) emits {{range $i, $v := .Items}}', () => {
1777
+ const result = compileAndGenerate(`function A({ items }: { items: string[] }) {
1778
+ return <ul>{items.entries().map(([i, v]) => <li key={i}>{i}: {v}</li>)}</ul>
1779
+ }
1780
+ export { A }`)
1781
+ expect(result.template).toContain('{{range $i, $v := .Items}}')
1782
+ })
1783
+
1784
+ test('.keys().map(k => ...) emits {{range $k, $_ := .Items}}', () => {
1785
+ const result = compileAndGenerate(`function A({ items }: { items: string[] }) {
1786
+ return <ul>{items.keys().map(k => <li key={k}>{k}</li>)}</ul>
1787
+ }
1788
+ export { A }`)
1789
+ expect(result.template).toContain('{{range $k, $_ := .Items}}')
1790
+ })
1791
+
1792
+ test('.values().map(v => ...) emits standard {{range $_, $v := .Items}}', () => {
1793
+ const result = compileAndGenerate(`function A({ items }: { items: string[] }) {
1794
+ return <ul>{items.values().map(v => <li key={v}>{v}</li>)}</ul>
1795
+ }
1796
+ export { A }`)
1797
+ expect(result.template).toContain('{{range $_, $v := .Items}}')
1798
+ })
1799
+ })
1674
1800
  })
1675
1801
 
1676
1802
  // =============================================================================
@@ -1708,6 +1834,10 @@ import { fixture as arraySortFieldDescFixture } from '../../../adapter-tests/fix
1708
1834
  import { fixture as arraySortPrimitiveFixture } from '../../../adapter-tests/fixtures/methods/array-sort-primitive'
1709
1835
  import { fixture as arraySortLocaleFixture } from '../../../adapter-tests/fixtures/methods/array-sort-locale'
1710
1836
  import { fixture as arrayToSortedFixture } from '../../../adapter-tests/fixtures/methods/array-toSorted'
1837
+ // #1448 Tier B — .entries / .keys / .values iteration shapes.
1838
+ import { fixture as arrayEntriesFixture } from '../../../adapter-tests/fixtures/methods/array-entries'
1839
+ import { fixture as arrayKeysFixture } from '../../../adapter-tests/fixtures/methods/array-keys'
1840
+ import { fixture as arrayValuesFixture } from '../../../adapter-tests/fixtures/methods/array-values'
1711
1841
 
1712
1842
  describe('GoTemplateAdapter - #1448 Tier A/B fixture-driven lowering pins', () => {
1713
1843
  const cases = [
@@ -1738,6 +1868,11 @@ describe('GoTemplateAdapter - #1448 Tier A/B fixture-driven lowering pins', () =
1738
1868
  { fixture: arraySortPrimitiveFixture, expect: 'bf_sort .Nums "self" "" "numeric" "asc"' },
1739
1869
  { fixture: arraySortLocaleFixture, expect: 'bf_sort .Names "self" "" "string" "asc"' },
1740
1870
  { fixture: arrayToSortedFixture, expect: 'bf_sort .Nums "self" "" "numeric" "asc"' },
1871
+ // #1448 Tier B — iteration shapes. These are loop-level
1872
+ // patterns (range binding order), not helper function calls.
1873
+ { fixture: arrayEntriesFixture, expect: '{{range $i, $v := .Items}}' },
1874
+ { fixture: arrayKeysFixture, expect: '{{range $k, $_ := .Items}}' },
1875
+ { fixture: arrayValuesFixture, expect: '{{range $_, $v := .Items}}' },
1741
1876
  ]
1742
1877
 
1743
1878
  for (const { fixture, expect: expectedHelper } of cases) {