tjs-lang 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.
Files changed (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,531 @@
1
+ /**
2
+ * Autocomplete Integration Tests
3
+ *
4
+ * Tests autocomplete behavior with real TJS code in various states.
5
+ * Uses transpilation to get metadata, then verifies completions.
6
+ */
7
+
8
+ import { describe, it, expect } from 'bun:test'
9
+ import { tjs } from '../../src/lang'
10
+
11
+ // Import the completion source internals for testing
12
+ // We'll test the logic directly rather than through CodeMirror
13
+
14
+ interface CompletionContext {
15
+ source: string
16
+ position: number // cursor position (use | in source to mark it)
17
+ metadata?: Record<string, any>
18
+ }
19
+
20
+ interface Completion {
21
+ label: string
22
+ type: string
23
+ detail?: string
24
+ }
25
+
26
+ // Helper: parse source with | marking cursor position
27
+ function parseSource(sourceWithCursor: string): {
28
+ source: string
29
+ position: number
30
+ } {
31
+ const position = sourceWithCursor.indexOf('|')
32
+ if (position === -1) {
33
+ return { source: sourceWithCursor, position: sourceWithCursor.length }
34
+ }
35
+ const source =
36
+ sourceWithCursor.slice(0, position) + sourceWithCursor.slice(position + 1)
37
+ return { source, position }
38
+ }
39
+
40
+ // Helper: transpile and extract metadata
41
+ function getMetadata(source: string): Record<string, any> | undefined {
42
+ try {
43
+ const result = tjs(source)
44
+ // tjs returns { code, types, metadata, ... }
45
+ return result?.metadata
46
+ } catch {
47
+ return undefined
48
+ }
49
+ }
50
+
51
+ // Helper: get object name before a dot
52
+ function getObjectBeforeDot(source: string, dotPos: number): string | null {
53
+ const before = source.slice(0, dotPos)
54
+ const match = before.match(/(\w+)\s*$/)
55
+ return match ? match[1] : null
56
+ }
57
+
58
+ // Curated property completions (simplified version of ajs-language.ts)
59
+ const PROPERTY_COMPLETIONS: Record<string, Completion[]> = {
60
+ console: [
61
+ { label: 'log', type: 'method', detail: '(...args: any[]) -> void' },
62
+ { label: 'error', type: 'method', detail: '(...args: any[]) -> void' },
63
+ { label: 'warn', type: 'method', detail: '(...args: any[]) -> void' },
64
+ { label: 'info', type: 'method', detail: '(...args: any[]) -> void' },
65
+ { label: 'debug', type: 'method', detail: '(...args: any[]) -> void' },
66
+ { label: 'table', type: 'method', detail: '(data: any) -> void' },
67
+ { label: 'clear', type: 'method', detail: '() -> void' },
68
+ ],
69
+ Math: [
70
+ { label: 'floor', type: 'method', detail: '(x: number) -> number' },
71
+ { label: 'ceil', type: 'method', detail: '(x: number) -> number' },
72
+ { label: 'round', type: 'method', detail: '(x: number) -> number' },
73
+ { label: 'abs', type: 'method', detail: '(x: number) -> number' },
74
+ { label: 'min', type: 'method', detail: '(...values: number[]) -> number' },
75
+ { label: 'max', type: 'method', detail: '(...values: number[]) -> number' },
76
+ { label: 'sqrt', type: 'method', detail: '(x: number) -> number' },
77
+ { label: 'pow', type: 'method', detail: '(base, exp) -> number' },
78
+ { label: 'random', type: 'method', detail: '() -> number' },
79
+ { label: 'PI', type: 'property', detail: 'number' },
80
+ { label: 'E', type: 'property', detail: 'number' },
81
+ ],
82
+ JSON: [
83
+ { label: 'parse', type: 'method', detail: '(text: string) -> any' },
84
+ { label: 'stringify', type: 'method', detail: '(value: any) -> string' },
85
+ ],
86
+ Object: [
87
+ { label: 'keys', type: 'method', detail: '(obj: object) -> string[]' },
88
+ { label: 'values', type: 'method', detail: '(obj: object) -> any[]' },
89
+ {
90
+ label: 'entries',
91
+ type: 'method',
92
+ detail: '(obj: object) -> [string, any][]',
93
+ },
94
+ {
95
+ label: 'assign',
96
+ type: 'method',
97
+ detail: '(target, ...sources) -> object',
98
+ },
99
+ ],
100
+ Array: [
101
+ { label: 'isArray', type: 'method', detail: '(value: any) -> boolean' },
102
+ { label: 'from', type: 'method', detail: '(iterable) -> any[]' },
103
+ ],
104
+ }
105
+
106
+ // Helper: get property completions for an object
107
+ function getPropertyCompletions(objName: string): Completion[] {
108
+ return PROPERTY_COMPLETIONS[objName] || []
109
+ }
110
+
111
+ // Simplified completion logic for testing (mirrors ajs-language.ts)
112
+ function getCompletions(ctx: CompletionContext): Completion[] {
113
+ const { source, position, metadata } = ctx
114
+
115
+ // Get word at cursor
116
+ let wordStart = position
117
+ while (wordStart > 0 && /[\w$]/.test(source[wordStart - 1])) {
118
+ wordStart--
119
+ }
120
+ const word = source.slice(wordStart, position)
121
+
122
+ // Get context before word
123
+ const charBefore = source.slice(Math.max(0, wordStart - 1), wordStart)
124
+ const lineStart = source.lastIndexOf('\n', wordStart - 1) + 1
125
+ const lineBefore = source.slice(lineStart, wordStart)
126
+
127
+ let completions: Completion[] = []
128
+
129
+ // After . - property access
130
+ if (charBefore === '.') {
131
+ const before = source.slice(Math.max(0, wordStart - 50), wordStart)
132
+ if (/expect\s*\([^)]*\)\s*\.$/.test(before)) {
133
+ completions = [
134
+ { label: 'toBe', type: 'method', detail: '(expected: any)' },
135
+ { label: 'toEqual', type: 'method', detail: '(expected: any)' },
136
+ { label: 'toContain', type: 'method', detail: '(item: any)' },
137
+ { label: 'toThrow', type: 'method', detail: '()' },
138
+ { label: 'toBeTruthy', type: 'method', detail: '()' },
139
+ { label: 'toBeFalsy', type: 'method', detail: '()' },
140
+ ]
141
+ } else {
142
+ // Property completions for known globals
143
+ const objName = getObjectBeforeDot(source, wordStart - 1)
144
+ if (objName) {
145
+ completions = getPropertyCompletions(objName)
146
+ }
147
+ }
148
+ }
149
+ // After : - type context
150
+ else if (/:\s*$/.test(lineBefore)) {
151
+ completions = [
152
+ { label: "''", type: 'type', detail: 'String type' },
153
+ { label: '0', type: 'type', detail: 'Number type' },
154
+ { label: 'true', type: 'type', detail: 'Boolean type' },
155
+ { label: 'null', type: 'type', detail: 'Null type' },
156
+ { label: '[]', type: 'type', detail: 'Array type' },
157
+ { label: '{}', type: 'type', detail: 'Object type' },
158
+ ]
159
+ }
160
+ // After -> - return type context
161
+ else if (/->\s*$/.test(lineBefore)) {
162
+ completions = [
163
+ { label: "''", type: 'type', detail: 'String type' },
164
+ { label: '0', type: 'type', detail: 'Number type' },
165
+ { label: 'true', type: 'type', detail: 'Boolean type' },
166
+ { label: '{}', type: 'type', detail: 'Object type' },
167
+ ]
168
+ }
169
+ // General context
170
+ else {
171
+ // Keywords
172
+ completions = [
173
+ { label: 'function', type: 'keyword' },
174
+ { label: 'const', type: 'keyword' },
175
+ { label: 'let', type: 'keyword' },
176
+ { label: 'if', type: 'keyword' },
177
+ { label: 'else', type: 'keyword' },
178
+ { label: 'while', type: 'keyword' },
179
+ { label: 'for', type: 'keyword' },
180
+ { label: 'return', type: 'keyword' },
181
+ { label: 'test', type: 'keyword' },
182
+ ]
183
+
184
+ // Global objects
185
+ completions.push(
186
+ { label: 'console', type: 'variable' },
187
+ { label: 'Math', type: 'variable' },
188
+ { label: 'JSON', type: 'variable' },
189
+ { label: 'Array', type: 'class' },
190
+ { label: 'Object', type: 'class' },
191
+ { label: 'String', type: 'class' },
192
+ { label: 'Number', type: 'class' },
193
+ { label: 'Date', type: 'class' },
194
+ { label: 'parseInt', type: 'function' },
195
+ { label: 'parseFloat', type: 'function' }
196
+ )
197
+
198
+ // Extract functions from source
199
+ const funcRegex = /function\s+(\w+)\s*\(([^)]*)\)/g
200
+ let match
201
+ while ((match = funcRegex.exec(source)) !== null) {
202
+ const [, name, params] = match
203
+ completions.push({ label: name, type: 'function', detail: `(${params})` })
204
+ }
205
+
206
+ // Extract variables before cursor
207
+ const before = source.slice(0, position)
208
+ const varRegex = /(?:const|let)\s+(\w+)\s*=/g
209
+ while ((match = varRegex.exec(before)) !== null) {
210
+ completions.push({ label: match[1], type: 'variable' })
211
+ }
212
+
213
+ // Add from metadata if available
214
+ if (metadata) {
215
+ const params = metadata.params
216
+ ? Object.keys(metadata.params).join(', ')
217
+ : ''
218
+ const returnType = metadata.returns?.type || 'any'
219
+ if (metadata.name) {
220
+ completions.push({
221
+ label: metadata.name,
222
+ type: 'function',
223
+ detail: `(${params}) -> ${returnType}`,
224
+ })
225
+ }
226
+ }
227
+ }
228
+
229
+ // Filter by prefix
230
+ if (word) {
231
+ const lowerWord = word.toLowerCase()
232
+ completions = completions.filter((c) =>
233
+ c.label.toLowerCase().startsWith(lowerWord)
234
+ )
235
+ }
236
+
237
+ return completions
238
+ }
239
+
240
+ describe('Autocomplete', () => {
241
+ describe('Type context (after :)', () => {
242
+ it('suggests types after colon in parameter', () => {
243
+ const { source, position } = parseSource('function foo(x: |)')
244
+ const completions = getCompletions({ source, position })
245
+
246
+ expect(completions.some((c) => c.label === "''")).toBe(true)
247
+ expect(completions.some((c) => c.label === '0')).toBe(true)
248
+ expect(completions.some((c) => c.label === 'true')).toBe(true)
249
+ expect(completions.every((c) => c.type === 'type')).toBe(true)
250
+ })
251
+
252
+ it('suggests types after colon with space', () => {
253
+ const { source, position } = parseSource('function foo(name: |)')
254
+ const completions = getCompletions({ source, position })
255
+
256
+ expect(completions.length).toBeGreaterThan(0)
257
+ expect(completions.every((c) => c.type === 'type')).toBe(true)
258
+ })
259
+ })
260
+
261
+ describe('Return type context (after ->)', () => {
262
+ it('suggests types after arrow', () => {
263
+ const { source, position } = parseSource('function foo(x: 0) -> |')
264
+ const completions = getCompletions({ source, position })
265
+
266
+ expect(completions.some((c) => c.label === '{}')).toBe(true)
267
+ expect(completions.every((c) => c.type === 'type')).toBe(true)
268
+ })
269
+ })
270
+
271
+ describe('General context', () => {
272
+ it('suggests keywords at start of file', () => {
273
+ const { source, position } = parseSource('|')
274
+ const completions = getCompletions({ source, position })
275
+
276
+ expect(completions.some((c) => c.label === 'function')).toBe(true)
277
+ expect(completions.some((c) => c.label === 'const')).toBe(true)
278
+ })
279
+
280
+ it('suggests keywords with prefix', () => {
281
+ const { source, position } = parseSource('func|')
282
+ const completions = getCompletions({ source, position })
283
+
284
+ expect(completions.some((c) => c.label === 'function')).toBe(true)
285
+ expect(completions.every((c) => c.label.startsWith('func'))).toBe(true)
286
+ })
287
+
288
+ it('suggests global objects like console', () => {
289
+ const { source, position } = parseSource('con|')
290
+ const completions = getCompletions({ source, position })
291
+
292
+ expect(completions.some((c) => c.label === 'console')).toBe(true)
293
+ expect(completions.some((c) => c.label === 'const')).toBe(true)
294
+ })
295
+
296
+ it('suggests Math, JSON, and other globals', () => {
297
+ const { source, position } = parseSource('|')
298
+ const completions = getCompletions({ source, position })
299
+
300
+ expect(completions.some((c) => c.label === 'Math')).toBe(true)
301
+ expect(completions.some((c) => c.label === 'JSON')).toBe(true)
302
+ expect(completions.some((c) => c.label === 'Array')).toBe(true)
303
+ expect(completions.some((c) => c.label === 'Object')).toBe(true)
304
+ expect(completions.some((c) => c.label === 'parseInt')).toBe(true)
305
+ })
306
+
307
+ it('suggests declared functions', () => {
308
+ const { source, position } = parseSource(`
309
+ function greet(name: '') {
310
+ return 'Hello ' + name
311
+ }
312
+
313
+ gr|
314
+ `)
315
+ const completions = getCompletions({ source, position })
316
+
317
+ expect(
318
+ completions.some((c) => c.label === 'greet' && c.type === 'function')
319
+ ).toBe(true)
320
+ })
321
+
322
+ it('suggests declared variables', () => {
323
+ const { source, position } = parseSource(`
324
+ const message = 'hello'
325
+ let count = 0
326
+
327
+ me|
328
+ `)
329
+ const completions = getCompletions({ source, position })
330
+
331
+ expect(
332
+ completions.some((c) => c.label === 'message' && c.type === 'variable')
333
+ ).toBe(true)
334
+ })
335
+
336
+ it('does not suggest variables declared after cursor', () => {
337
+ const { source, position } = parseSource(`
338
+ const before = 1
339
+ |
340
+ const after = 2
341
+ `)
342
+ const completions = getCompletions({ source, position })
343
+
344
+ expect(completions.some((c) => c.label === 'before')).toBe(true)
345
+ expect(completions.some((c) => c.label === 'after')).toBe(false)
346
+ })
347
+ })
348
+
349
+ describe('Expect matchers (after expect().)', () => {
350
+ it('suggests matchers after expect().', () => {
351
+ const { source, position } = parseSource('expect(value).|')
352
+ const completions = getCompletions({ source, position })
353
+
354
+ expect(completions.some((c) => c.label === 'toBe')).toBe(true)
355
+ expect(completions.some((c) => c.label === 'toEqual')).toBe(true)
356
+ expect(completions.some((c) => c.label === 'toThrow')).toBe(true)
357
+ })
358
+
359
+ it('filters matchers by prefix', () => {
360
+ const { source, position } = parseSource('expect(x).toB|')
361
+ const completions = getCompletions({ source, position })
362
+
363
+ expect(completions.every((c) => c.label.startsWith('toB'))).toBe(true)
364
+ expect(completions.some((c) => c.label === 'toBe')).toBe(true)
365
+ expect(completions.some((c) => c.label === 'toBeTruthy')).toBe(true)
366
+ })
367
+ })
368
+
369
+ describe('Property completions (after object.)', () => {
370
+ it('suggests console methods after console.', () => {
371
+ const { source, position } = parseSource('console.|')
372
+ const completions = getCompletions({ source, position })
373
+
374
+ expect(completions.some((c) => c.label === 'log')).toBe(true)
375
+ expect(completions.some((c) => c.label === 'error')).toBe(true)
376
+ expect(completions.some((c) => c.label === 'warn')).toBe(true)
377
+ expect(completions.every((c) => c.type === 'method')).toBe(true)
378
+ })
379
+
380
+ it('suggests Math methods and constants after Math.', () => {
381
+ const { source, position } = parseSource('Math.|')
382
+ const completions = getCompletions({ source, position })
383
+
384
+ expect(completions.some((c) => c.label === 'floor')).toBe(true)
385
+ expect(completions.some((c) => c.label === 'ceil')).toBe(true)
386
+ expect(completions.some((c) => c.label === 'random')).toBe(true)
387
+ expect(
388
+ completions.some((c) => c.label === 'PI' && c.type === 'property')
389
+ ).toBe(true)
390
+ })
391
+
392
+ it('suggests JSON methods after JSON.', () => {
393
+ const { source, position } = parseSource('JSON.|')
394
+ const completions = getCompletions({ source, position })
395
+
396
+ expect(completions.some((c) => c.label === 'parse')).toBe(true)
397
+ expect(completions.some((c) => c.label === 'stringify')).toBe(true)
398
+ })
399
+
400
+ it('suggests Object methods after Object.', () => {
401
+ const { source, position } = parseSource('Object.|')
402
+ const completions = getCompletions({ source, position })
403
+
404
+ expect(completions.some((c) => c.label === 'keys')).toBe(true)
405
+ expect(completions.some((c) => c.label === 'values')).toBe(true)
406
+ expect(completions.some((c) => c.label === 'entries')).toBe(true)
407
+ })
408
+
409
+ it('filters property completions by prefix', () => {
410
+ const { source, position } = parseSource('console.lo|')
411
+ const completions = getCompletions({ source, position })
412
+
413
+ expect(completions.some((c) => c.label === 'log')).toBe(true)
414
+ // Should filter to only those starting with 'lo'
415
+ expect(completions.every((c) => c.label.startsWith('lo'))).toBe(true)
416
+ })
417
+
418
+ it('returns empty for unknown objects', () => {
419
+ const { source, position } = parseSource('unknownObj.|')
420
+ const completions = getCompletions({ source, position })
421
+
422
+ expect(completions.length).toBe(0)
423
+ })
424
+ })
425
+
426
+ describe('With transpiled metadata', () => {
427
+ it('extracts function signature from transpiled code', () => {
428
+ const source = `function add(a: 0, b: 0) -> 0 {
429
+ return a + b
430
+ }`
431
+ const metadata = getMetadata(source)
432
+
433
+ expect(metadata).toBeDefined()
434
+ // metadata is now keyed by function name
435
+ expect(metadata?.add?.params?.a?.type?.kind).toBe('number')
436
+ expect(metadata?.add?.params?.b?.type?.kind).toBe('number')
437
+ })
438
+
439
+ it('extracts example-based types', () => {
440
+ const source = `function greet(name: 'World', times = 1) -! '' {
441
+ return 'Hello ' + name
442
+ }`
443
+ const metadata = getMetadata(source)
444
+
445
+ expect(metadata).toBeDefined()
446
+ // metadata is now keyed by function name
447
+ expect(metadata?.greet?.params?.name?.type?.kind).toBe('string')
448
+ expect(metadata?.greet?.params?.times?.type?.kind).toBe('number')
449
+ expect(metadata?.greet?.params?.times?.default).toBe(1)
450
+ })
451
+
452
+ it('handles object return types', () => {
453
+ const source = `function createUser(name: '', age: 0) -> { name: '', age: 0 } {
454
+ return { name, age }
455
+ }`
456
+ const metadata = getMetadata(source)
457
+
458
+ expect(metadata).toBeDefined()
459
+ // metadata is now keyed by function name
460
+ expect(metadata?.createUser?.returns?.kind).toBe('object')
461
+ })
462
+ })
463
+
464
+ describe('Real-world code examples', () => {
465
+ it('handles incomplete function definition', () => {
466
+ const { source, position } = parseSource(`
467
+ function processOrder(order: |
468
+ `)
469
+ const completions = getCompletions({ source, position })
470
+
471
+ // Should suggest types even with incomplete syntax
472
+ expect(completions.some((c) => c.type === 'type')).toBe(true)
473
+ })
474
+
475
+ it('handles code mid-edit', () => {
476
+ const { source, position } = parseSource(`
477
+ function ship(to: '12345', quantity = 1) {
478
+ const result = httpFetch({ url: '/api/ship' })
479
+ ret|
480
+ }
481
+ `)
482
+ const completions = getCompletions({ source, position })
483
+
484
+ // typing 'ret' should suggest 'return' (starts with 'ret')
485
+ expect(completions.some((c) => c.label === 'return')).toBe(true)
486
+ // 'result' doesn't start with 'ret', so it shouldn't be suggested
487
+ expect(completions.some((c) => c.label === 'result')).toBe(false)
488
+ })
489
+
490
+ it('suggests variables when typing their prefix', () => {
491
+ const { source, position } = parseSource(`
492
+ function ship(to: '12345', quantity = 1) {
493
+ const result = httpFetch({ url: '/api/ship' })
494
+ res|
495
+ }
496
+ `)
497
+ const completions = getCompletions({ source, position })
498
+
499
+ // typing 'res' should suggest 'result'
500
+ expect(completions.some((c) => c.label === 'result')).toBe(true)
501
+ })
502
+
503
+ it('suggests variables declared in same scope', () => {
504
+ const { source, position } = parseSource(`
505
+ const x = 1
506
+ const y = 2
507
+ |
508
+ `)
509
+ const completions = getCompletions({ source, position })
510
+
511
+ expect(completions.some((c) => c.label === 'x')).toBe(true)
512
+ expect(completions.some((c) => c.label === 'y')).toBe(true)
513
+ })
514
+
515
+ it('handles test block context', () => {
516
+ const { source, position } = parseSource(`
517
+ function add(a: 0, b: 0) {
518
+ return a + b
519
+ }
520
+
521
+ test('add works') {
522
+ const result = add(1, 2)
523
+ expect(result).|
524
+ }
525
+ `)
526
+ const completions = getCompletions({ source, position })
527
+
528
+ expect(completions.some((c) => c.label === 'toBe')).toBe(true)
529
+ })
530
+ })
531
+ })