Skip to content

Commit 739e525

Browse files
committed
Add some tests
1 parent 3fc179b commit 739e525

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed

apps/vscode/src/providers/semantic-tokens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ function remapModifierBitfield(
159159
* Remap token type/modifier indices from source legend to target legend
160160
* Only maps types that exist in both legends (standard types only)
161161
*/
162-
function remapTokenIndices(
162+
export function remapTokenIndices(
163163
tokens: SemanticTokens,
164164
sourceLegend: { tokenTypes: string[]; tokenModifiers: string[]; },
165165
targetLegend: { tokenTypes: string[]; tokenModifiers: string[]; }
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import * as vscode from "vscode";
2+
import * as assert from "assert";
3+
import { decodeSemanticTokens, encodeSemanticTokens, remapTokenIndices } from "../providers/semantic-tokens";
4+
5+
suite("Semantic Tokens", function () {
6+
7+
test("Encode and decode semantic tokens roundtrip", function () {
8+
// Create a set of semantic tokens in absolute format
9+
const tokens = [
10+
{ line: 0, startChar: 0, length: 3, tokenType: 1, tokenModifiers: 0 },
11+
{ line: 0, startChar: 4, length: 4, tokenType: 2, tokenModifiers: 1 },
12+
{ line: 2, startChar: 2, length: 5, tokenType: 3, tokenModifiers: 2 },
13+
];
14+
15+
// Encode to delta format
16+
const encoded = encodeSemanticTokens(tokens);
17+
18+
// Verify the encoded data has the expected structure
19+
assert.ok(encoded.data, "Encoded tokens should have data property");
20+
assert.strictEqual(encoded.data.length, 15, "Encoded data should have 15 elements (3 tokens x 5 fields)");
21+
22+
// Decode back to absolute format
23+
const decoded = decodeSemanticTokens(encoded);
24+
25+
// Verify roundtrip produces original tokens
26+
assert.deepStrictEqual(decoded, tokens, "Decoded tokens should match original tokens");
27+
});
28+
29+
test("Decode semantic tokens with delta encoding", function () {
30+
// Create semantic tokens in delta-encoded format
31+
// Format: [deltaLine, deltaStartChar, length, tokenType, tokenModifiers, ...]
32+
const deltaEncoded: vscode.SemanticTokens = {
33+
data: new Uint32Array([
34+
0, 5, 3, 1, 0, // line 0, char 5, length 3
35+
0, 4, 4, 2, 1, // line 0 (0+0), char 9 (5+4), length 4
36+
2, 0, 5, 3, 2, // line 2 (0+2), char 0 (reset), length 5
37+
]),
38+
resultId: undefined
39+
};
40+
41+
const decoded = decodeSemanticTokens(deltaEncoded);
42+
43+
// Verify the decoded tokens have correct absolute positions
44+
assert.strictEqual(decoded.length, 3, "Should decode 3 tokens");
45+
assert.deepStrictEqual(decoded[0], { line: 0, startChar: 5, length: 3, tokenType: 1, tokenModifiers: 0 });
46+
assert.deepStrictEqual(decoded[1], { line: 0, startChar: 9, length: 4, tokenType: 2, tokenModifiers: 1 });
47+
assert.deepStrictEqual(decoded[2], { line: 2, startChar: 0, length: 5, tokenType: 3, tokenModifiers: 2 });
48+
});
49+
50+
test("Legend mapping remaps matching token types", function () {
51+
// Source legend has types at different indices than target
52+
const sourceLegend = {
53+
tokenTypes: ["class", "function", "variable"],
54+
tokenModifiers: ["readonly", "static"]
55+
};
56+
57+
const targetLegend = {
58+
tokenTypes: ["variable", "function", "class"], // Different order
59+
tokenModifiers: ["static", "readonly"] // Different order
60+
};
61+
62+
// Create tokens using source indices
63+
const sourceTokens = encodeSemanticTokens([
64+
{ line: 0, startChar: 0, length: 3, tokenType: 0, tokenModifiers: 0 }, // "class" in source
65+
{ line: 1, startChar: 0, length: 4, tokenType: 1, tokenModifiers: 0 }, // "function" in source
66+
{ line: 2, startChar: 0, length: 5, tokenType: 2, tokenModifiers: 0 }, // "variable" in source
67+
]);
68+
69+
// Remap to target legend
70+
const remapped = remapTokenIndices(sourceTokens, sourceLegend, targetLegend);
71+
const decoded = decodeSemanticTokens(remapped);
72+
73+
// Verify types were remapped to target indices
74+
assert.strictEqual(decoded.length, 3, "Should have 3 tokens");
75+
assert.strictEqual(decoded[0].tokenType, 2, "class should map to index 2 in target");
76+
assert.strictEqual(decoded[1].tokenType, 1, "function should map to index 1 in target");
77+
assert.strictEqual(decoded[2].tokenType, 0, "variable should map to index 0 in target");
78+
});
79+
80+
test("Legend mapping filters out unmapped token types", function () {
81+
// Source legend has types that don't exist in target
82+
const sourceLegend = {
83+
tokenTypes: ["class", "customType", "function", "anotherCustomType"],
84+
tokenModifiers: []
85+
};
86+
87+
const targetLegend = {
88+
tokenTypes: ["class", "function"], // Only has class and function
89+
tokenModifiers: []
90+
};
91+
92+
// Create tokens including unmapped types
93+
const sourceTokens = encodeSemanticTokens([
94+
{ line: 0, startChar: 0, length: 3, tokenType: 0, tokenModifiers: 0 }, // "class" - should be kept
95+
{ line: 1, startChar: 0, length: 4, tokenType: 1, tokenModifiers: 0 }, // "customType" - should be filtered
96+
{ line: 2, startChar: 0, length: 5, tokenType: 2, tokenModifiers: 0 }, // "function" - should be kept
97+
{ line: 3, startChar: 0, length: 6, tokenType: 3, tokenModifiers: 0 }, // "anotherCustomType" - should be filtered
98+
]);
99+
100+
// Remap to target legend
101+
const remapped = remapTokenIndices(sourceTokens, sourceLegend, targetLegend);
102+
const decoded = decodeSemanticTokens(remapped);
103+
104+
// Verify unmapped types were filtered out
105+
assert.strictEqual(decoded.length, 2, "Should have 2 tokens after filtering");
106+
assert.strictEqual(decoded[0].tokenType, 0, "class should map to index 0");
107+
assert.strictEqual(decoded[0].line, 0, "First token should be from line 0");
108+
assert.strictEqual(decoded[1].tokenType, 1, "function should map to index 1");
109+
assert.strictEqual(decoded[1].line, 2, "Second token should be from line 2");
110+
});
111+
112+
test("Legend mapping remaps modifier bitfields correctly", function () {
113+
// Source and target have modifiers in different positions
114+
const sourceLegend = {
115+
tokenTypes: ["function"],
116+
tokenModifiers: ["readonly", "static", "async"] // indices 0, 1, 2
117+
};
118+
119+
const targetLegend = {
120+
tokenTypes: ["function"],
121+
tokenModifiers: ["async", "readonly", "static"] // indices 0, 1, 2 (reordered)
122+
};
123+
124+
// Create token with multiple modifiers set
125+
// In source: readonly=bit 0, static=bit 1, async=bit 2
126+
const modifierBits = (1 << 0) | (1 << 1); // readonly + static in source
127+
const sourceTokens = encodeSemanticTokens([
128+
{ line: 0, startChar: 0, length: 3, tokenType: 0, tokenModifiers: modifierBits }
129+
]);
130+
131+
// Remap to target legend
132+
const remapped = remapTokenIndices(sourceTokens, sourceLegend, targetLegend);
133+
const decoded = decodeSemanticTokens(remapped);
134+
135+
// In target: async=bit 0, readonly=bit 1, static=bit 2
136+
const expectedModifiers = (1 << 1) | (1 << 2); // readonly + static in target
137+
assert.strictEqual(decoded[0].tokenModifiers, expectedModifiers, "Modifiers should be remapped to target positions");
138+
});
139+
140+
test("Legend mapping handles modifiers not in target legend", function () {
141+
// Source has modifiers that don't exist in target
142+
const sourceLegend = {
143+
tokenTypes: ["function"],
144+
tokenModifiers: ["readonly", "customModifier", "static"]
145+
};
146+
147+
const targetLegend = {
148+
tokenTypes: ["function"],
149+
tokenModifiers: ["readonly", "static"] // Missing "customModifier"
150+
};
151+
152+
// Set all three modifiers in source
153+
const modifierBits = (1 << 0) | (1 << 1) | (1 << 2); // all three modifiers
154+
const sourceTokens = encodeSemanticTokens([
155+
{ line: 0, startChar: 0, length: 3, tokenType: 0, tokenModifiers: modifierBits }
156+
]);
157+
158+
// Remap to target legend
159+
const remapped = remapTokenIndices(sourceTokens, sourceLegend, targetLegend);
160+
const decoded = decodeSemanticTokens(remapped);
161+
162+
// Only readonly and static should be mapped; customModifier should be dropped
163+
const expectedModifiers = (1 << 0) | (1 << 1); // readonly + static in target
164+
assert.strictEqual(decoded[0].tokenModifiers, expectedModifiers, "Unmapped modifiers should be filtered out");
165+
});
166+
167+
});

0 commit comments

Comments
 (0)