11/*
22 * Jest tests for inspectTokenScopesHandler functionality in VSCode extension.
33 * Uses real vscode-textmate and vscode-oniguruma libraries for actual grammar testing.
4+ *
5+ * Token locations use format: "line:startCol-endCol" (1-based)
46 */
57
68import * as path from 'path' ;
79import * as fs from 'fs' ;
8- import { tokenizeContent } from '../commands/inspectTokenScopes' ;
9-
10- // Read the actual app.jac file
11- const appJacPath = path . join ( process . cwd ( ) , 'examples' , 'app.jac' ) ;
12- const appJacContent = fs . readFileSync ( appJacPath , 'utf-8' ) ;
13-
14- // Paths for grammar and wasm
15- const grammarPath = path . join ( process . cwd ( ) , 'syntaxes' , 'jac.tmLanguage.json' ) ;
16- const wasmPath = path . join ( process . cwd ( ) , 'node_modules' , 'vscode-oniguruma' , 'release' , 'onig.wasm' ) ;
17-
18- describe ( 'inspectTokenScopesHandler' , ( ) => {
19- test ( 'should tokenize Jac keywords correctly' , async ( ) => {
20- const tokenMap = await tokenizeContent ( appJacContent , grammarPath , wasmPath ) ;
21-
22- // Check 'with' keyword (storage.type.function.jac)
23- const withScopes = tokenMap . get ( 'with' ) ;
24- expect ( withScopes ) . toBeDefined ( ) ;
25- expect ( withScopes ) . toContain ( 'source.jac' ) ;
26- expect ( withScopes ) . toContain ( 'storage.type.function.jac' ) ;
27-
28- // Check 'entry' keyword
29- const entryScopes = tokenMap . get ( 'entry' ) ;
30- expect ( entryScopes ) . toBeDefined ( ) ;
31- expect ( entryScopes ) . toContain ( 'keyword.control.flow.jac' ) ;
32-
33- // Check 'lambda' keyword
34- const lambdaScopes = tokenMap . get ( 'lambda' ) ;
35- expect ( lambdaScopes ) . toBeDefined ( ) ;
36- expect ( lambdaScopes ) . toContain ( 'keyword.control.flow.jac' ) ;
37-
38- // Check 'print' builtin
39- const printScopes = tokenMap . get ( 'print' ) ;
40- expect ( printScopes ) . toBeDefined ( ) ;
41- expect ( printScopes ) . toContain ( 'support.function.builtin.jac' ) ;
42- } ) ;
43-
44- test ( 'should tokenize JSX elements correctly' , async ( ) => {
45- const tokenMap = await tokenizeContent ( appJacContent , grammarPath , wasmPath ) ;
46-
47- // Check that we have JSX-related tokens
48- const allScopes = Array . from ( tokenMap . values ( ) ) . flat ( ) ;
49-
50- // Check for meta.jsx scope or entity.name.tag for JSX elements
51- const hasJsxScopes = allScopes . some ( s =>
52- s . includes ( 'jsx' ) ||
53- s . includes ( 'entity.name.tag' ) ||
54- s . includes ( 'punctuation.definition.tag' )
55- ) ;
56- expect ( hasJsxScopes ) . toBe ( true ) ;
57-
58- // Check specific JSX HTML tags
59- const divScopes = tokenMap . get ( 'div' ) ;
60- expect ( divScopes ) . toBeDefined ( ) ;
61- expect ( divScopes ) . toContain ( 'entity.name.tag.html.jsx.jac' ) ;
62-
63- const h1Scopes = tokenMap . get ( 'h1' ) ;
64- expect ( h1Scopes ) . toBeDefined ( ) ;
65- expect ( h1Scopes ) . toContain ( 'entity.name.tag.html.jsx.jac' ) ;
66-
67- const buttonScopes = tokenMap . get ( 'button' ) ;
68- expect ( buttonScopes ) . toBeDefined ( ) ;
69- expect ( buttonScopes ) . toContain ( 'entity.name.tag.html.jsx.jac' ) ;
70-
71- // Check JSX attributes
72- const onClickScopes = tokenMap . get ( 'onClick' ) ;
73- expect ( onClickScopes ) . toBeDefined ( ) ;
74- expect ( onClickScopes ) . toContain ( 'entity.other.attribute-name.jsx.jac' ) ;
75-
76- // Check PascalCase component names
77- const buttonComponentScopes = tokenMap . get ( 'ButtonComponent' ) ;
78- expect ( buttonComponentScopes ) . toBeDefined ( ) ;
79- expect ( buttonComponentScopes ) . toContain ( 'support.class.component.jsx.jac' ) ;
80-
81- const navLinkScopes = tokenMap . get ( 'NavLink' ) ;
82- expect ( navLinkScopes ) . toBeDefined ( ) ;
83- expect ( navLinkScopes ) . toContain ( 'support.class.component.jsx.jac' ) ;
84- } ) ;
85- } ) ;
10+ import {
11+ tokenizeContent ,
12+ TokenizeResult ,
13+ TokenInfo
14+ } from '../commands/inspectTokenScopes' ;
15+
16+ /** Get token at a specific location */
17+ function getTokenByLocation (
18+ result : TokenizeResult ,
19+ line : number ,
20+ startCol : number ,
21+ endCol : number
22+ ) : TokenInfo | undefined {
23+ return result . byLocation . get ( `${ line } :${ startCol } -${ endCol } ` ) ;
24+ }
25+
26+ // Test fixture paths
27+ const EXAMPLES_DIR = path . join ( process . cwd ( ) , 'examples' ) ;
28+ const GRAMMAR_PATH = path . join ( process . cwd ( ) , 'syntaxes' , 'jac.tmLanguage.json' ) ;
29+ const WASM_PATH = path . join ( process . cwd ( ) , 'node_modules' , 'vscode-oniguruma' , 'release' , 'onig.wasm' ) ;
30+
31+ // Load test fixture
32+ const appJacContent = fs . readFileSync ( path . join ( EXAMPLES_DIR , 'app.jac' ) , 'utf-8' ) ;
33+
34+ /**
35+ * Helper to assert a token has expected text and contains expected scopes
36+ */
37+ function expectToken (
38+ result : TokenizeResult ,
39+ line : number ,
40+ startCol : number ,
41+ endCol : number ,
42+ expectedText : string ,
43+ expectedScopes : string [ ]
44+ ) : void {
45+ const token = getTokenByLocation ( result , line , startCol , endCol ) ;
46+ expect ( token ) . toBeDefined ( ) ;
47+ expect ( token ! . text ) . toBe ( expectedText ) ;
48+ for ( const scope of expectedScopes ) {
49+ expect ( token ! . scopes ) . toContain ( scope ) ;
50+ }
51+ }
52+
53+ describe ( 'inspectTokenScopesHandler - Location Based Tests' , ( ) => {
54+ let result : TokenizeResult ;
55+
56+ beforeAll ( async ( ) => {
57+ result = await tokenizeContent ( appJacContent , GRAMMAR_PATH , WASM_PATH ) ;
58+ } ) ;
59+
60+ describe ( 'Jac Keywords' , ( ) => {
61+ test ( 'cl keyword at line 1' , ( ) => {
62+ // cl {
63+ expectToken ( result , 1 , 1 , 3 , 'cl' , [ 'source.jac' , 'storage.modifier.declaration.jac' ] ) ;
64+ } ) ;
65+
66+ test ( 'def keyword' , ( ) => {
67+ // def app() -> any {
68+ expectToken ( result , 3 , 5 , 8 , 'def' , [ 'source.jac' , 'meta.function.jac' , 'storage.type.function.jac' ] ) ;
69+ } ) ;
70+
71+ test ( 'return keyword' , ( ) => {
72+ // return <div>
73+ expectToken ( result , 5 , 9 , 15 , 'return' , [ 'source.jac' , 'keyword.control.flow.jac' ] ) ;
74+ } ) ;
75+
76+ test ( 'lambda keyword' , ( ) => {
77+ // lambda e: any -> None { ... }
78+ expectToken ( result , 8 , 30 , 36 , 'lambda' , [ 'source.jac' , 'keyword.control.flow.jac' ] ) ;
79+ } ) ;
80+
81+ test ( 'with keyword' , ( ) => {
82+ // with entry{
83+ expectToken ( result , 37 , 1 , 5 , 'with' , [ 'source.jac' , 'storage.type.function.jac' ] ) ;
84+ } ) ;
85+
86+ test ( 'entry keyword' , ( ) => {
87+ // with entry{
88+ expectToken ( result , 37 , 6 , 11 , 'entry' , [ 'source.jac' , 'keyword.control.flow.jac' ] ) ;
89+ } ) ;
90+ } ) ;
91+
92+ describe ( 'Builtin Functions' , ( ) => {
93+ test ( 'print builtin function' , ( ) => {
94+ // print("Hello, Jac!");
95+ expectToken ( result , 38 , 5 , 10 , 'print' , [ 'source.jac' , 'support.function.builtin.jac' ] ) ;
96+ } ) ;
97+ } ) ;
98+
99+ describe ( 'JSX HTML Tags (lowercase)' , ( ) => {
100+ test ( 'div opening tag' , ( ) => {
101+ // <div>
102+ expectToken ( result , 5 , 17 , 20 , 'div' , [ 'entity.name.tag.html.jsx.jac' ] ) ;
103+ } ) ;
104+
105+ test ( 'h1 opening tag' , ( ) => {
106+ // <h1>Hello, World!</h1>
107+ expectToken ( result , 6 , 14 , 16 , 'h1' , [ 'entity.name.tag.html.jsx.jac' ] ) ;
108+ } ) ;
109+
110+ test ( 'p opening tag' , ( ) => {
111+ // <p>Count: {count}</p>
112+ expectToken ( result , 7 , 14 , 15 , 'p' , [ 'entity.name.tag.html.jsx.jac' ] ) ;
113+ } ) ;
114+
115+ test ( 'button opening tag' , ( ) => {
116+ // <button onClick={...}>
117+ expectToken ( result , 8 , 14 , 20 , 'button' , [ 'entity.name.tag.html.jsx.jac' ] ) ;
118+ } ) ;
119+ } ) ;
120+
121+ describe ( 'JSX Component Tags (PascalCase)' , ( ) => {
122+ test ( 'ButtonComponent tag' , ( ) => {
123+ // <ButtonComponent label="Click Me" />
124+ expectToken ( result , 11 , 14 , 29 , 'ButtonComponent' , [ 'support.class.component.jsx.jac' ] ) ;
125+ } ) ;
126+
127+ test ( 'NavLink opening tag' , ( ) => {
128+ // <NavLink to="/about">
129+ expectToken ( result , 12 , 14 , 21 , 'NavLink' , [ 'support.class.component.jsx.jac' ] ) ;
130+ } ) ;
131+ } ) ;
132+
133+ describe ( 'JSX Attributes' , ( ) => {
134+ test ( 'onClick attribute' , ( ) => {
135+ // <button onClick={...}>
136+ expectToken ( result , 8 , 21 , 28 , 'onClick' , [ 'entity.other.attribute-name.jsx.jac' ] ) ;
137+ } ) ;
138+
139+ test ( 'label attribute' , ( ) => {
140+ // <ButtonComponent label="Click Me" />
141+ expectToken ( result , 11 , 30 , 35 , 'label' , [ 'entity.other.attribute-name.jsx.jac' ] ) ;
142+ } ) ;
143+
144+ test ( 'to attribute' , ( ) => {
145+ // <NavLink to="/about">
146+ expectToken ( result , 12 , 22 , 24 , 'to' , [ 'entity.other.attribute-name.jsx.jac' ] ) ;
147+ } ) ;
148+ } ) ;
149+
150+ describe ( 'JSX Attribute Strings' , ( ) => {
151+ test ( 'string attribute value - Click Me' , ( ) => {
152+ // label="Click Me"
153+ expectToken ( result , 11 , 37 , 45 , 'Click Me' , [ 'string.quoted.double.jac' ] ) ;
154+ } ) ;
155+
156+ test ( 'string attribute value - /about' , ( ) => {
157+ // to="/about"
158+ expectToken ( result , 12 , 26 , 32 , '/about' , [ 'string.quoted.double.jac' ] ) ;
159+ } ) ;
160+ } ) ;
161+
162+ describe ( 'Keyword Escape Syntax' , ( ) => {
163+ test ( '<>esc keyword escape' , ( ) => {
164+ // a = <>esc;
165+ // esc is at columns 11-14 (1-based)
166+ const escToken = getTokenByLocation ( result , 19 , 11 , 14 ) ;
167+ expect ( escToken ) . toBeDefined ( ) ;
168+ expect ( escToken ! . text ) . toBe ( 'esc' ) ;
169+ expect ( escToken ! . scopes ) . toContain ( 'variable.other.escaped.jac' ) ;
170+ } ) ;
171+
172+ test ( '<> punctuation for keyword escape' , ( ) => {
173+ // a = <>esc;
174+ // <> is at columns 9-11 (1-based)
175+ const punctToken = getTokenByLocation ( result , 19 , 9 , 11 ) ;
176+ expect ( punctToken ) . toBeDefined ( ) ;
177+ expect ( punctToken ! . text ) . toBe ( '<>' ) ;
178+ expect ( punctToken ! . scopes ) . toContain ( 'punctuation.definition.keyword-escape.jac' ) ;
179+ } ) ;
180+ } ) ;
181+
182+ describe ( 'JSX Fragments' , ( ) => {
183+ test ( 'fragment opening tag <>' , ( ) => {
184+ // <>
185+ // <div>First</div>
186+ // </>
187+ const fragmentOpen = getTokenByLocation ( result , 22 , 13 , 15 ) ;
188+ expect ( fragmentOpen ) . toBeDefined ( ) ;
189+ expect ( fragmentOpen ! . text ) . toBe ( '<>' ) ;
190+ expect ( fragmentOpen ! . scopes ) . toContain ( 'punctuation.definition.tag.jsx.jac' ) ;
191+ } ) ;
192+
193+ test ( 'fragment closing tag </>' , ( ) => {
194+ const fragmentClose = getTokenByLocation ( result , 29 , 13 , 16 ) ;
195+ expect ( fragmentClose ) . toBeDefined ( ) ;
196+ expect ( fragmentClose ! . text ) . toBe ( '</>' ) ;
197+ expect ( fragmentClose ! . scopes ) . toContain ( 'punctuation.definition.tag.jsx.jac' ) ;
198+ } ) ;
199+ } ) ;
200+
201+ describe ( 'Types' , ( ) => {
202+ test ( 'any type annotation' , ( ) => {
203+ // def app() -> any {
204+ expectToken ( result , 3 , 18 , 21 , 'any' , [ 'source.jac' , 'support.type.jac' ] ) ;
205+ } ) ;
206+ } ) ;
207+
208+ describe ( 'Strings' , ( ) => {
209+ test ( 'string literal - Hello, Jac!' , ( ) => {
210+ // print("Hello, Jac!");
211+ expectToken ( result , 38 , 12 , 23 , 'Hello, Jac!' , [ 'string.quoted.single.jac' ] ) ;
212+ } ) ;
213+ } ) ;
214+ } ) ;
0 commit comments