lecodes-sdk 1.2.0 → 2.0.1

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.
Files changed (260) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +8 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Scene.d.ts +41 -13
  25. package/dist/types/gl/Texture.d.ts +1 -1
  26. package/dist/types/gl/animation/Locomotion.d.ts +8 -1
  27. package/dist/types/inject.d.ts +11 -11
  28. package/dist/types/inject.editor.d.ts +1 -0
  29. package/dist/types/net/core.d.ts +7 -0
  30. package/dist/types/plugin.d.ts +76 -0
  31. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  32. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  33. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  34. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  35. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  36. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  37. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  38. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  39. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  40. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  41. package/dist/types/runtime/app.d.ts +9 -2
  42. package/dist/types/runtime/fetch.d.ts +2 -0
  43. package/dist/types/runtime/input.d.ts +1 -1
  44. package/dist/types/runtime/media.d.ts +6 -10
  45. package/dist/types/runtime/misc.d.ts +4 -1
  46. package/dist/types/runtime/net.d.ts +3 -2
  47. package/dist/types/runtime/touch.d.ts +32 -0
  48. package/dist/types/scene/defineScene.d.ts +43 -2
  49. package/dist/types/scene/editor.d.ts +52 -0
  50. package/dist/types/scene/gizmos.d.ts +7 -4
  51. package/dist/types/ui/NativeView.d.ts +6 -4
  52. package/dist/types/ui/UI.d.ts +1 -1
  53. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  54. package/dist/types/ui/UIButton.d.ts +12 -14
  55. package/dist/types/ui/UIContainer.d.ts +0 -6
  56. package/dist/types/ui/UIImage.d.ts +1 -4
  57. package/dist/types/ui/UIInput.d.ts +8 -24
  58. package/dist/types/ui/UIModal.d.ts +0 -2
  59. package/dist/types/ui/UINode.d.ts +70 -56
  60. package/dist/types/ui/UIPager.d.ts +28 -27
  61. package/dist/types/ui/UIPopover.d.ts +0 -2
  62. package/dist/types/ui/UIScreen.d.ts +12 -17
  63. package/dist/types/ui/UIScrollable.d.ts +1 -4
  64. package/dist/types/ui/UIText.d.ts +0 -2
  65. package/dist/types/ui/UIVideo.d.ts +3 -5
  66. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  67. package/dist/types/ui/UIWidget.d.ts +10 -9
  68. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  69. package/dist/types/ui/presentable.d.ts +46 -32
  70. package/dist/types/ui/router.d.ts +18 -7
  71. package/dist/types/ui/styleColor.d.ts +1 -0
  72. package/dist/types/ui/transitions.d.ts +18 -0
  73. package/dist/types/ui/tree.d.ts +75 -0
  74. package/dist/types/version.d.ts +10 -0
  75. package/dist/types.json +1 -1
  76. package/package.json +12 -3
  77. package/prompts/2d.md +2 -6
  78. package/prompts/3d.md +1 -5
  79. package/prompts/README.md +1 -1
  80. package/prompts/canvas.md +9 -8
  81. package/prompts/compose.ts +1 -1
  82. package/prompts/core.md +3 -3
  83. package/prompts/design.md +1 -1
  84. package/prompts/dist/2d-game.md +473 -239
  85. package/prompts/dist/3d-app.md +553 -205
  86. package/prompts/dist/ar-app.md +435 -202
  87. package/prompts/dist/design.md +113 -95
  88. package/prompts/dist/ui-app.md +386 -170
  89. package/prompts/ui-design.md +2 -3
  90. package/prompts/ui.md +45 -37
  91. package/src/animate/tween/Animation.ts +34 -150
  92. package/src/animate/tween/Timeline.ts +175 -175
  93. package/src/animate/tween/animateValue.ts +6 -3
  94. package/src/animate/tween/easing.ts +10 -3
  95. package/src/animate/tween/spec.ts +41 -15
  96. package/src/audio/Sound.ts +3 -3
  97. package/src/audio/audio.ts +2 -1
  98. package/src/bridges/2d.d.ts +317 -0
  99. package/src/bridges/app.d.ts +91 -0
  100. package/src/bridges/audio.d.ts +97 -0
  101. package/src/bridges/canvas.d.ts +79 -0
  102. package/src/bridges/device.d.ts +72 -0
  103. package/src/bridges/fetch.d.ts +80 -0
  104. package/src/bridges/files.d.ts +70 -0
  105. package/src/bridges/gl.d.ts +1133 -0
  106. package/src/bridges/input.d.ts +72 -0
  107. package/src/bridges/media.d.ts +55 -0
  108. package/src/bridges/nav.d.ts +71 -0
  109. package/src/bridges/net.d.ts +52 -0
  110. package/src/bridges/service.d.ts +52 -0
  111. package/src/bridges/socket.d.ts +31 -0
  112. package/src/bridges/storage.d.ts +33 -0
  113. package/src/bridges/tree.d.ts +301 -0
  114. package/src/bridges/types.d.ts +49 -0
  115. package/src/canvas/Canvas.ts +114 -159
  116. package/src/canvas/gen/cssColor.ts +224 -0
  117. package/src/canvas/gen/recorder.ts +212 -0
  118. package/src/canvas/gen/spec.ts +201 -0
  119. package/src/chisel.ts +193 -0
  120. package/src/compile/assetMacro.ts +1 -1
  121. package/src/compile/bundler.ts +11 -2
  122. package/src/compile/compileProject.ts +43 -4
  123. package/src/compile/fontMacro.ts +3 -4
  124. package/src/compile/header.ts +26 -5
  125. package/src/compile/index.ts +3 -1
  126. package/src/compile/liteMaterial.ts +1 -1
  127. package/src/compile/sceneEditor.ts +11 -26
  128. package/src/core/color.ts +73 -30
  129. package/src/core/pins.ts +51 -0
  130. package/src/core/signals.ts +8 -1
  131. package/src/g2/CharacterController2D.ts +3 -3
  132. package/src/g2/Node2D.ts +57 -39
  133. package/src/g2/Physics2D.ts +2 -2
  134. package/src/g2/Scene2D.ts +35 -23
  135. package/src/g2/Texture2D.ts +1 -1
  136. package/src/g2/loop.ts +4 -4
  137. package/src/gl/CameraPlace.ts +52 -52
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +10 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Mesh.ts +120 -120
  144. package/src/gl/Model.ts +21 -16
  145. package/src/gl/Node.ts +91 -24
  146. package/src/gl/Particles.ts +1 -1
  147. package/src/gl/Scene.ts +100 -44
  148. package/src/gl/Texture.ts +8 -7
  149. package/src/gl/animation/AnimationClip.ts +1 -1
  150. package/src/gl/animation/DynamicBone.ts +482 -482
  151. package/src/gl/animation/Locomotion.ts +8 -3
  152. package/src/gl/nav/NavMesh.ts +3 -4
  153. package/src/gl/physics/Physics.ts +2 -2
  154. package/src/gl/physics/physicsEvents.ts +3 -3
  155. package/src/gl/scenarios.ts +291 -291
  156. package/src/gl/terrain/Terrain.ts +4 -5
  157. package/src/gl/touch.ts +14 -15
  158. package/src/host.d.ts +2 -3
  159. package/src/inject.editor.ts +7 -0
  160. package/src/inject.ts +13 -16
  161. package/src/net/core.ts +6 -5
  162. package/src/net/index.ts +1 -1
  163. package/src/net/replication.ts +1 -1
  164. package/src/plugin.ts +191 -0
  165. package/src/plugins/gen/camera/contract.d.ts +27 -0
  166. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  167. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  168. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  169. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  170. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  171. package/src/plugins/gen/map/contract.d.ts +144 -0
  172. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  173. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  174. package/src/plugins/gen/push/contract.d.ts +61 -0
  175. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  176. package/src/plugins/gen/push/sdk/push.ts +105 -0
  177. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  178. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  179. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  180. package/src/plugins/permission.ts +5 -4
  181. package/src/runtime/app.ts +20 -9
  182. package/src/runtime/appEvents.ts +5 -4
  183. package/src/runtime/channel.ts +18 -15
  184. package/src/runtime/clipboard.ts +4 -3
  185. package/src/runtime/datetime.ts +2 -1
  186. package/src/runtime/device.ts +17 -15
  187. package/src/runtime/fetch.ts +30 -20
  188. package/src/runtime/files.ts +16 -15
  189. package/src/runtime/input.ts +12 -10
  190. package/src/runtime/media.ts +50 -46
  191. package/src/runtime/misc.ts +7 -3
  192. package/src/runtime/net.ts +8 -7
  193. package/src/runtime/rpc.ts +1 -3
  194. package/src/runtime/service.ts +19 -14
  195. package/src/runtime/share.ts +4 -3
  196. package/src/runtime/storage.ts +6 -4
  197. package/src/runtime/touch.ts +32 -0
  198. package/src/scene/defineScene.ts +61 -365
  199. package/src/scene/editor.ts +408 -0
  200. package/src/scene/editorPlugins.ts +3 -3
  201. package/src/scene/gizmos.ts +15 -9
  202. package/src/server/db/marci/query.ts +1 -1
  203. package/src/server/host.ts +1 -1
  204. package/src/server/runtime.ts +1 -1
  205. package/src/ui/NativeView.ts +72 -25
  206. package/src/ui/UI.ts +3 -3
  207. package/src/ui/UIBottomSheet.ts +16 -17
  208. package/src/ui/UIButton.ts +54 -16
  209. package/src/ui/UIContainer.ts +0 -6
  210. package/src/ui/UIImage.ts +34 -30
  211. package/src/ui/UIInput.ts +29 -37
  212. package/src/ui/UIModal.ts +1 -3
  213. package/src/ui/UINode.ts +347 -297
  214. package/src/ui/UIPager.ts +93 -78
  215. package/src/ui/UIPopover.ts +0 -2
  216. package/src/ui/UIScreen.ts +41 -36
  217. package/src/ui/UIScrollable.ts +19 -13
  218. package/src/ui/UISpacer.ts +1 -1
  219. package/src/ui/UITabs.ts +8 -6
  220. package/src/ui/UIText.ts +7 -17
  221. package/src/ui/UIVideo.ts +30 -27
  222. package/src/ui/UIVirtualizedList.ts +58 -59
  223. package/src/ui/UIWidget.ts +38 -18
  224. package/src/ui/colorKeys.gen.ts +37 -0
  225. package/src/ui/fonts.ts +2 -2
  226. package/src/ui/presentable.ts +59 -42
  227. package/src/ui/router.ts +52 -35
  228. package/src/ui/styleColor.ts +56 -0
  229. package/src/ui/theme.ts +7 -6
  230. package/src/ui/transitions.ts +249 -0
  231. package/src/ui/tree.ts +346 -0
  232. package/src/version.ts +24 -0
  233. package/tests/helpers/engineWorld.ts +11 -0
  234. package/tests/helpers/fakeTree.ts +353 -0
  235. package/tests/helpers/hostStubs.ts +31 -0
  236. package/tests/helpers/index.ts +14 -0
  237. package/tests/helpers/memoryMarci.ts +124 -0
  238. package/tests/helpers/phases.ts +23 -0
  239. package/tests/helpers/preload.ts +18 -0
  240. package/tests/helpers/stubApp.ts +2 -0
  241. package/tests/helpers/stubDevice.ts +2 -0
  242. package/tests/helpers/stubFetch.ts +2 -0
  243. package/tests/helpers/stubInput.ts +2 -0
  244. package/dist/inject.js +0 -4629
  245. package/dist/types/core/registry.d.ts +0 -7
  246. package/dist/types/plugins/camera.d.ts +0 -25
  247. package/dist/types/plugins/push.d.ts +0 -46
  248. package/src/bridges.d.ts +0 -1769
  249. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  250. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  251. package/src/compile/__tests__/assetName.test.ts +0 -55
  252. package/src/compile/__tests__/compile.test.ts +0 -310
  253. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  254. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  255. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  256. package/src/core/__tests__/stateMachine.test.ts +0 -132
  257. package/src/core/registry.ts +0 -23
  258. package/src/plugins/camera.ts +0 -81
  259. package/src/plugins/push.ts +0 -132
  260. package/src/plugins/qr.ts +0 -73
@@ -1,219 +0,0 @@
1
- import { test, expect, describe } from "bun:test"
2
- import { applyAssetIconMacro, buildIconsHeaderValue, iconNotFoundError, rewriteAssetIcons, scanAssetIconCalls, suggestIconNames } from "../assetIconMacro"
3
-
4
- const svg = (stroke: string) => `<svg viewBox="0 0 24 24"><path stroke="${stroke}" d="M1 1"/></svg>`
5
-
6
- describe("scanAssetIconCalls", () => {
7
- test("a bare call → id + pack + name, no color, correct span", () => {
8
- const src = `UIImage(assetIcon("lucide:bell"))`
9
- const [c] = scanAssetIconCalls(src)
10
- expect(c.id).toBe("lucide:bell")
11
- expect(c.pack).toBe("lucide")
12
- expect(c.name).toBe("bell")
13
- expect(c.bake).toBeUndefined()
14
- expect(c.tint).toBeUndefined()
15
- expect(c.error).toBeUndefined()
16
- expect(src.slice(c.start, c.end)).toBe(`assetIcon("lucide:bell")`)
17
- })
18
-
19
- test("a literal color option → captured as a compile-time bake", () => {
20
- const [c] = scanAssetIconCalls(`assetIcon('lucide:check', { color: "#5b8cff" })`)
21
- expect(c.id).toBe("lucide:check")
22
- expect(c.bake).toBe("#5b8cff")
23
- expect(c.tint).toBeUndefined()
24
- expect(c.error).toBeUndefined()
25
- })
26
-
27
- test("a token / expression color → captured verbatim as a runtime tint (not an error)", () => {
28
- const [c] = scanAssetIconCalls(`assetIcon("lucide:bell", { color: tokens.accent })`)
29
- expect(c.tint).toBe("tokens.accent")
30
- expect(c.bake).toBeUndefined()
31
- expect(c.error).toBeUndefined()
32
- })
33
-
34
- test("dashed pack/name ids are valid", () => {
35
- const [c] = scanAssetIconCalls(`assetIcon("simple-icons:arrow-right")`)
36
- expect(c.pack).toBe("simple-icons")
37
- expect(c.name).toBe("arrow-right")
38
- expect(c.error).toBeUndefined()
39
- })
40
-
41
- test("a non-literal id → error", () => {
42
- const [c] = scanAssetIconCalls(`assetIcon(iconVar)`)
43
- expect(c.error).toContain("string-literal id")
44
- })
45
-
46
- test("a malformed id → error naming pack:name", () => {
47
- const [c] = scanAssetIconCalls(`assetIcon("BellIcon")`)
48
- expect(c.error).toContain("pack:name")
49
- })
50
-
51
- test("a non-object opts → error", () => {
52
- const [c] = scanAssetIconCalls(`assetIcon("lucide:bell", "#fff")`)
53
- expect(c.error).toContain("object literal")
54
- })
55
-
56
- test("a method named assetIcon is not matched", () => {
57
- expect(scanAssetIconCalls(`icons.assetIcon("lucide:bell")`)).toEqual([])
58
- })
59
-
60
- test("assetIcon inside a string or comment is ignored", () => {
61
- expect(scanAssetIconCalls(`const s = "assetIcon('lucide:bell')" // assetIcon('lucide:x')`)).toEqual([])
62
- })
63
-
64
- test("surrounding template literals don't confuse the scan", () => {
65
- const src = "UIBox([]).style({ border: `2px solid ${c.muted}` })\nUIImage(assetIcon('lucide:bell'))"
66
- const calls = scanAssetIconCalls(src)
67
- expect(calls).toHaveLength(1)
68
- expect(calls[0].id).toBe("lucide:bell")
69
- })
70
-
71
- test("multiple calls are all found in order", () => {
72
- const calls = scanAssetIconCalls(`assetIcon("lucide:bell"); assetIcon("lucide:check")`)
73
- expect(calls.map((c) => c.id)).toEqual(["lucide:bell", "lucide:check"])
74
- })
75
- })
76
-
77
- describe("rewriteAssetIcons", () => {
78
- test("rewrites a call to an inline SvgSource(...)", () => {
79
- const src = `UIImage(assetIcon("lucide:bell"))`
80
- const out = rewriteAssetIcons(src, scanAssetIconCalls(src), new Map([["lucide:bell", svg("currentColor")]]))
81
- expect(out).toBe("UIImage(SvgSource(`" + svg("currentColor") + "`))")
82
- expect(out).not.toContain("assetIcon")
83
- })
84
-
85
- test("bakes a literal color (currentColor → color)", () => {
86
- const src = `assetIcon("lucide:bell", { color: "#f00" })`
87
- const out = rewriteAssetIcons(src, scanAssetIconCalls(src), new Map([["lucide:bell", svg("currentColor")]]))
88
- expect(out).toContain(`stroke="#f00"`)
89
- expect(out).not.toContain("currentColor")
90
- expect(out).not.toContain("tintColor")
91
- })
92
-
93
- test("a token color → source-level tint, SVG left as currentColor", () => {
94
- const src = `assetIcon("lucide:bell", { color: colors.accent })`
95
- const out = rewriteAssetIcons(src, scanAssetIconCalls(src), new Map([["lucide:bell", svg("currentColor")]]))
96
- expect(out).toContain("Object.assign(SvgSource(`")
97
- expect(out).toContain("{ tintColor: (colors.accent) }")
98
- expect(out).toContain("currentColor")
99
- })
100
-
101
- test("escapes backticks and ${ in the SVG body", () => {
102
- const nasty = "<svg>`${danger}`</svg>"
103
- const src = `assetIcon("lucide:bell")`
104
- const out = rewriteAssetIcons(src, scanAssetIconCalls(src), new Map([["lucide:bell", nasty]]))
105
- expect(out).toContain("\\`")
106
- expect(out).toContain("\\${")
107
- })
108
-
109
- test("two calls in one file are both rewritten (indices stay valid)", () => {
110
- const src = `[assetIcon("lucide:bell"), assetIcon("lucide:check")]`
111
- const map = new Map([["lucide:bell", svg("a")], ["lucide:check", svg("b")]])
112
- const out = rewriteAssetIcons(src, scanAssetIconCalls(src), map)
113
- expect(out).not.toContain("assetIcon")
114
- expect(out).toContain(svg("a"))
115
- expect(out).toContain(svg("b"))
116
- })
117
- })
118
-
119
- describe("applyAssetIconMacro", () => {
120
- test("rewrites every text entry via the resolver and returns the resolved ids", async () => {
121
- const entries = [
122
- { path: "/screens/home.ts", type: "text", text: `export default UIImage(assetIcon("lucide:bell"))` },
123
- { path: "/shared/icon.ts", type: "text", text: `export const c = () => UIImage(assetIcon("lucide:check", { color: "#0f0" }))` },
124
- { path: "/logo.png", type: "resource", text: undefined },
125
- ]
126
- let asked: string[] = []
127
- const ids = await applyAssetIconMacro(entries, (want) => {
128
- asked = want
129
- return new Map(want.map((id) => [id, svg("currentColor")]))
130
- })
131
- expect(ids.sort()).toEqual(["lucide:bell", "lucide:check"])
132
- expect(asked.sort()).toEqual(["lucide:bell", "lucide:check"])
133
- expect(entries[0].text).toContain("SvgSource(`")
134
- expect(entries[0].text).not.toContain("assetIcon")
135
- expect(entries[1].text).toContain(`stroke="#0f0"`)
136
- })
137
-
138
- test("a no-op when nothing uses assetIcon — the resolver is never called", async () => {
139
- const entries = [{ path: "/x.ts", type: "text", text: `export default UIScreen([])` }]
140
- const ids = await applyAssetIconMacro(entries, () => { throw new Error("resolver must not run") })
141
- expect(ids).toEqual([])
142
- expect(entries[0].text).toBe(`export default UIScreen([])`)
143
- })
144
-
145
- test("a malformed call aborts before the resolver runs", async () => {
146
- const entries = [{ path: "/x.ts", type: "text", text: `UIImage(assetIcon(runtimeVar))` }]
147
- await expect(applyAssetIconMacro(entries, () => new Map())).rejects.toThrow(/string-literal id/)
148
- })
149
-
150
- test("an id the resolver silently omits fails the pass", async () => {
151
- const entries = [{ path: "/x.ts", type: "text", text: `UIImage(assetIcon("lucide:ghost"))` }]
152
- await expect(applyAssetIconMacro(entries, () => new Map())).rejects.toThrow(/could not resolve icon/)
153
- })
154
- })
155
-
156
- describe("buildIconsHeaderValue", () => {
157
- test("distinct packs, sorted", () => {
158
- expect(buildIconsHeaderValue(["noto:star", "lucide:bell", "lucide:check"])).toBe("lucide;noto")
159
- expect(buildIconsHeaderValue([])).toBe("")
160
- })
161
- })
162
-
163
- describe("suggestIconNames", () => {
164
- const icons = {
165
- "bell": { kw: ["bell"] },
166
- "bell-ring": { kw: ["bell", "ring"] },
167
- "bell-off": { kw: ["bell", "off"] },
168
- "check": { kw: ["check"] },
169
- "arrow-right": { kw: ["arrow", "right"] },
170
- }
171
-
172
- test("a typo resolves by edit distance, exact-ish first", () => {
173
- const s = suggestIconNames("bel", icons)
174
- expect(s[0]).toBe("bell")
175
- expect(s).toContain("bell-off") // substring family follows
176
- })
177
-
178
- test("a sub-word matches by substring", () => {
179
- expect(suggestIconNames("ring", icons)).toContain("bell-ring")
180
- })
181
-
182
- test("swapped word order matches via keywords", () => {
183
- expect(suggestIconNames("right-arrow", icons)).toContain("arrow-right")
184
- })
185
-
186
- test("nothing similar → empty", () => {
187
- expect(suggestIconNames("zzz-qqq", icons)).toEqual([])
188
- })
189
- })
190
-
191
- describe("iconNotFoundError", () => {
192
- const registry: Record<string, string> = {
193
- "lucide/meta.json": JSON.stringify({ icons: { bell: { kw: ["bell"] }, "bell-ring": { kw: ["bell", "ring"] } } }),
194
- "licenses.json": JSON.stringify({ lucide: {}, tabler: {} }),
195
- }
196
- const read = async (rel: string) => registry[rel] ?? null
197
-
198
- test("a known pack + missing name → did-you-mean from the pack's meta", async () => {
199
- const err = await iconNotFoundError("lucide", "bel", read)
200
- expect(err.message).toContain(`"lucide:bel" not found`)
201
- expect(err.message).toContain(`"lucide:bell"`)
202
- })
203
-
204
- test("a known pack, nothing similar → says so", async () => {
205
- const err = await iconNotFoundError("lucide", "zzz-qqq", read)
206
- expect(err.message).toContain("No similar name")
207
- })
208
-
209
- test("an unknown pack → the available-pack list", async () => {
210
- const err = await iconNotFoundError("nope", "bell", read)
211
- expect(err.message).toContain(`unknown icon pack "nope"`)
212
- expect(err.message).toContain("lucide, tabler")
213
- })
214
-
215
- test("nothing reachable → a connectivity error naming the registry", async () => {
216
- const err = await iconNotFoundError("lucide", "bell", async () => null, "https://reg.example/v1")
217
- expect(err.message).toContain("can't reach the icon registry (https://reg.example/v1)")
218
- })
219
- })
@@ -1,100 +0,0 @@
1
- import { test, expect } from "bun:test"
2
- import { collectAssetMacros, desugarAssetMacros } from "../assetMacro"
3
- import { compileProject } from "../compileProject"
4
-
5
- // A missing asset is a COMPILE ERROR — never a fallback. The old behaviour compiled
6
- // `asset('../x.bake')` whose file didn't exist into the source path verbatim: a string nothing
7
- // resolves at runtime, handed to the app as a broken reference with no diagnostic at all (and,
8
- // since the macro appends its generated imports, a const used before its own declaration — a TDZ
9
- // ReferenceError at boot that read like a bundler crash).
10
-
11
- const paths = (src: string) => collectAssetMacros(src, "/main.ts").map(r => r.path)
12
-
13
- test("a missing asset fails the compile, naming the file and the call site", async () => {
14
- const main = "const scene = new Scene()\nconst bake = asset('./assets/lightmap.bake')\nconsole.log(scene, bake)\n"
15
- const compile = compileProject({
16
- name: "Missing",
17
- publicUrl: "https://le.codes",
18
- entries: [ { path: "/main.ts", type: "text", text: main } ],
19
- })
20
- await expect(compile).rejects.toThrow("asset not found: ./assets/lightmap.bake (main.ts:2)")
21
- })
22
-
23
- test("the same asset present in the entries compiles to its URL", async () => {
24
- const main = "const bake = asset('./assets/lightmap.bake')\nconsole.log(bake)\n"
25
- const js = await compileProject({
26
- name: "Present",
27
- publicUrl: "https://le.codes",
28
- entries: [
29
- { path: "/main.ts", type: "text", text: main },
30
- { path: "/assets/lightmap.bake", type: "resource", fileSrc: "/u/lightmap.bake" },
31
- ],
32
- })
33
- expect(js).toContain("https://le.codes/u/lightmap.bake")
34
- expect(js).not.toContain("./assets/lightmap.bake")
35
- })
36
-
37
- // Only the modules the bundle reaches are checked: a project keeps one entry per level, and a
38
- // level whose art is not on this machine must not fail an entry that never imports it.
39
- test("a missing asset in a module the entry never imports does not fail the compile", async () => {
40
- const js = await compileProject({
41
- name: "Unreached",
42
- publicUrl: "https://le.codes",
43
- entries: [
44
- { path: "/main.ts", type: "text", text: "const scene = new Scene()\nconsole.log(scene)\n" },
45
- { path: "/levels/lost.ts", type: "text", text: "export const lost = asset('./lost.glb')\n" },
46
- ],
47
- })
48
- expect(js).not.toContain("lost.glb")
49
- })
50
-
51
- test("the same missing asset fails once the entry imports its module", async () => {
52
- const compile = compileProject({
53
- name: "Reached",
54
- publicUrl: "https://le.codes",
55
- entries: [
56
- { path: "/main.ts", type: "text", text: "import { lost } from './levels/lost'\nconsole.log(lost)\n" },
57
- { path: "/levels/lost.ts", type: "text", text: "export const lost = asset('./lost.glb')\n" },
58
- ],
59
- })
60
- await expect(compile).rejects.toThrow("asset not found: ./lost.glb (levels/lost.ts:1)")
61
- })
62
-
63
- test("a commented-out asset() is not an asset at all", () => {
64
- const src = [
65
- "/** The atlas image — write `asset('./TileSet.png')`. */",
66
- "// const old = asset('./deleted.png')",
67
- "const live = asset('./hero.png')",
68
- "/* asset('./block-commented.png') */",
69
- ].join("\n")
70
- expect(paths(src)).toEqual([ "./hero.png" ])
71
- const out = desugarAssetMacros(src, "/main.ts")
72
- expect(out).toContain(`import __asset_0 from "./hero.png";`)
73
- expect(out).not.toContain("deleted.png\";")
74
- expect(out).toContain("asset('./deleted.png')") // the comment itself is untouched
75
- })
76
-
77
- test("an asset() inside a string literal is text, one inside `${…}` is a call", () => {
78
- expect(paths(`const doc = "call asset('./x.png') to load"`)).toEqual([])
79
- expect(paths("const url = `bg:${asset('./bg.png')}`")).toEqual([ "./bg.png" ])
80
- expect(paths("const url = `not ${1} asset('./no.png')`")).toEqual([])
81
- })
82
-
83
- test("method calls named asset are still untouched, dynamic arguments still throw", () => {
84
- expect(paths(`state.model = ui.asset("model")`)).toEqual([])
85
- expect(() => paths("const p = './x.png'\nconst a = asset(p)")).toThrow("expects a string literal")
86
- })
87
-
88
- test("repeated paths share one import and the line numbers stay exact", () => {
89
- const src = "const a = asset('./x.png')\n\nconst b = asset('./x.png')\nconst c = asset('./y.png')\n"
90
- const refs = collectAssetMacros(src, "/main.ts")
91
- expect(refs.map(r => r.line)).toEqual([ 1, 3, 4 ])
92
- const out = desugarAssetMacros(src, "/main.ts")
93
- // The generated imports are APPENDED, so no original line moves (source maps stay honest).
94
- const lines = out.split("\n")
95
- expect(lines.slice(0, 4)).toEqual([ "const a = __asset_0", "", "const b = __asset_0", "const c = __asset_1" ])
96
- expect(lines.slice(4).join("\n")).toContain("import __asset_0")
97
- expect(out).toContain("const a = __asset_0")
98
- expect(out).toContain("const b = __asset_0")
99
- expect(out).toContain("const c = __asset_1")
100
- })
@@ -1,55 +0,0 @@
1
- import { test, expect } from "bun:test"
2
- import { isCanonicalAssetName, sanitizeAssetName } from "../assetName"
3
-
4
- // The invariant: two names that LOOK the same on screen must be stored the same way, or the import
5
- // written from what the user sees resolves to nothing. Codepoints are spelled out via fromCharCode
6
- // so the cases stay readable — the characters themselves are invisible in an editor.
7
-
8
- const NBSP = String.fromCharCode(0x00a0)
9
- const NARROW_NBSP = String.fromCharCode(0x202f)
10
- const ZERO_WIDTH = String.fromCharCode(0x200b)
11
- const RTL_MARK = String.fromCharCode(0x200f)
12
- const COMBINING_BREVE = String.fromCharCode(0x0306)
13
-
14
- test("a non-breaking space becomes the plain space the user types in an import", () => {
15
- expect(sanitizeAssetName(`итальянский${NBSP}сквер1.jpg`)).toBe("итальянский сквер1.jpg")
16
- expect(sanitizeAssetName(`narrow${NARROW_NBSP}space.png`)).toBe("narrow space.png")
17
- })
18
-
19
- test("a decomposed name (what macOS hands out) composes to the same codepoints as typed text", () => {
20
- // "й" as и + combining breve — indistinguishable on screen from the single U+0439 codepoint.
21
- const decomposed = `и${COMBINING_BREVE}ога.glb`
22
- expect(decomposed).not.toBe("йога.glb")
23
- expect(sanitizeAssetName(decomposed)).toBe("йога.glb")
24
- })
25
-
26
- test("zero-width and bidi characters are dropped", () => {
27
- expect(sanitizeAssetName(`logo${ZERO_WIDTH}.png`)).toBe("logo.png")
28
- expect(sanitizeAssetName(`${RTL_MARK}logo.png`)).toBe("logo.png")
29
- })
30
-
31
- test("path separators and Windows-illegal characters can't enter a name", () => {
32
- expect(sanitizeAssetName("a/b.png")).toBe("a_b.png")
33
- expect(sanitizeAssetName("a\\b.png")).toBe("a_b.png")
34
- expect(sanitizeAssetName('q?:*"<>|.png')).toBe("q_______.png")
35
- })
36
-
37
- test("surrounding whitespace and trailing dots go; a leading dot stays", () => {
38
- expect(sanitizeAssetName(" main.ts ")).toBe("main.ts")
39
- expect(sanitizeAssetName("main.ts...")).toBe("main.ts")
40
- expect(sanitizeAssetName(".gitignore")).toBe(".gitignore")
41
- expect(sanitizeAssetName("two spaces.png")).toBe("two spaces.png")
42
- })
43
-
44
- test("an ordinary name is returned untouched and sanitizing is idempotent", () => {
45
- for (const name of [ "main.ts", "итальянский сквер1.jpg", "My Scene.scene", "ui-kit.mat" ]) {
46
- expect(sanitizeAssetName(name)).toBe(name)
47
- expect(isCanonicalAssetName(name)).toBe(true)
48
- expect(sanitizeAssetName(sanitizeAssetName(name))).toBe(sanitizeAssetName(name))
49
- }
50
- })
51
-
52
- test("a name with nothing printable left comes back empty (callers reject it)", () => {
53
- expect(sanitizeAssetName(` ${ZERO_WIDTH}${NBSP} `)).toBe("")
54
- expect(isCanonicalAssetName(`x${ZERO_WIDTH}.png`)).toBe(false)
55
- })