Skip to content

Commit bbed414

Browse files
committed
Write tests with agent
1 parent 1c4ce34 commit bbed414

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
3+
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import * as vscode from 'vscode';
8+
import { codePointOffsetFromUtf16Index, JupyterPositronPosition, JupyterPositronRange, JupyterPositronLocation } from '../jupyter/TypesConverters';
9+
10+
suite('TypesConverters', () => {
11+
suite('codePointOffsetFromUtf16Index', () => {
12+
test('Empty string', () => {
13+
assert.strictEqual(codePointOffsetFromUtf16Index('', 0), 0);
14+
assert.strictEqual(codePointOffsetFromUtf16Index('', 5), 0);
15+
});
16+
17+
test('Negative index', () => {
18+
assert.strictEqual(codePointOffsetFromUtf16Index('hello', -1), 0);
19+
assert.strictEqual(codePointOffsetFromUtf16Index('hello', -10), 0);
20+
});
21+
22+
test('Zero index', () => {
23+
assert.strictEqual(codePointOffsetFromUtf16Index('hello', 0), 0);
24+
assert.strictEqual(codePointOffsetFromUtf16Index('😀', 0), 0);
25+
});
26+
27+
test('ASCII text', () => {
28+
const text = 'hello';
29+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
30+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 1);
31+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 2);
32+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 3);
33+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 4);
34+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 5), 5);
35+
});
36+
37+
test('Index beyond string length', () => {
38+
const text = 'hi';
39+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 10), 2);
40+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 100), 2);
41+
});
42+
43+
test('Single emoji', () => {
44+
const text = '😀';
45+
// '😀' is 2 UTF-16 units, 1 code point
46+
assert.strictEqual(text.length, 2, 'UTF-16 length should be 2');
47+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
48+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 0, 'Index at high surrogate should not count emoji');
49+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 1, 'Index after emoji should count it');
50+
});
51+
52+
test('Emoji at start', () => {
53+
const text = '😀abc';
54+
// '😀' = 2 units, then 3 ASCII chars
55+
assert.strictEqual(text.length, 5);
56+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
57+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 0, 'Middle of emoji');
58+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 1, 'After emoji');
59+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 2, 'After emoji + 1 char');
60+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 3);
61+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 5), 4);
62+
});
63+
64+
test('Emoji at end', () => {
65+
const text = 'abc😀';
66+
assert.strictEqual(text.length, 5);
67+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
68+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 1);
69+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 2);
70+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 3, 'Before emoji');
71+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 3, 'Middle of emoji');
72+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 5), 4, 'After emoji');
73+
});
74+
75+
test('Emoji in middle', () => {
76+
const text = 'a😀b';
77+
assert.strictEqual(text.length, 4);
78+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
79+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 1, 'After a');
80+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 1, 'Middle of emoji');
81+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 2, 'After emoji');
82+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 3, 'After b');
83+
});
84+
85+
test('Multiple emojis', () => {
86+
const text = '😀😁😂';
87+
// Each emoji is 2 UTF-16 units
88+
assert.strictEqual(text.length, 6);
89+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
90+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 0, 'Middle of first emoji');
91+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 1, 'After first emoji');
92+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 1, 'Middle of second emoji');
93+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 2, 'After second emoji');
94+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 5), 2, 'Middle of third emoji');
95+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 6), 3, 'After third emoji');
96+
});
97+
98+
test('Mixed ASCII and emojis', () => {
99+
const text = 'Hi😀!';
100+
// H=1, i=1, 😀=2, !=1 => 5 UTF-16 units, 4 code points
101+
assert.strictEqual(text.length, 5);
102+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
103+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 1);
104+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 2, 'After "Hi"');
105+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 2, 'Middle of emoji');
106+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 3, 'After emoji');
107+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 5), 4, 'After !');
108+
});
109+
110+
test('Non-BMP characters (Chinese)', () => {
111+
// U+20000 is a CJK Ideograph Extension B character (surrogate pair)
112+
const text = '\u{20000}ab';
113+
assert.strictEqual(text.length, 4, '2 for surrogate pair + 2 ASCII');
114+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
115+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 0, 'Middle of surrogate pair');
116+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 1, 'After surrogate pair');
117+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 2);
118+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 4), 3);
119+
});
120+
121+
test('BMP special characters', () => {
122+
// These are within BMP (1 UTF-16 unit each)
123+
const text = '€£¥';
124+
assert.strictEqual(text.length, 3);
125+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 0), 0);
126+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 1), 1);
127+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 2), 2);
128+
assert.strictEqual(codePointOffsetFromUtf16Index(text, 3), 3);
129+
});
130+
});
131+
132+
suite('JupyterPositronPosition', () => {
133+
test('ASCII text at line start', () => {
134+
const position = new vscode.Position(0, 0);
135+
const text = 'hello world';
136+
const result = JupyterPositronPosition.from(position, text);
137+
138+
assert.strictEqual(result.line, 0);
139+
assert.strictEqual(result.character, 0);
140+
});
141+
142+
test('ASCII text mid-line', () => {
143+
const position = new vscode.Position(0, 5);
144+
const text = 'hello world';
145+
const result = JupyterPositronPosition.from(position, text);
146+
147+
assert.strictEqual(result.line, 0);
148+
assert.strictEqual(result.character, 5);
149+
});
150+
151+
test('Line number is preserved', () => {
152+
const position = new vscode.Position(10, 5);
153+
const text = 'hello';
154+
const result = JupyterPositronPosition.from(position, text);
155+
156+
assert.strictEqual(result.line, 10);
157+
assert.strictEqual(result.character, 5);
158+
});
159+
160+
test('Text with emoji - position after emoji', () => {
161+
const position = new vscode.Position(0, 2);
162+
const text = '😀a';
163+
const result = JupyterPositronPosition.from(position, text);
164+
165+
assert.strictEqual(result.line, 0);
166+
assert.strictEqual(result.character, 1, 'Should count emoji as 1 code point');
167+
});
168+
169+
test('Text with emoji - position in middle of emoji', () => {
170+
const position = new vscode.Position(0, 1);
171+
const text = '😀a';
172+
const result = JupyterPositronPosition.from(position, text);
173+
174+
assert.strictEqual(result.line, 0);
175+
assert.strictEqual(result.character, 0, 'Should not count partial emoji');
176+
});
177+
178+
test('Text with multiple emojis', () => {
179+
const position = new vscode.Position(0, 4);
180+
const text = '😀😁ab';
181+
const result = JupyterPositronPosition.from(position, text);
182+
183+
assert.strictEqual(result.line, 0);
184+
assert.strictEqual(result.character, 2, 'Should count 2 emojis as 2 code points');
185+
});
186+
});
187+
188+
suite('JupyterPositronRange', () => {
189+
test('ASCII text range', () => {
190+
const range = new vscode.Range(
191+
new vscode.Position(0, 0),
192+
new vscode.Position(0, 5)
193+
);
194+
const text = 'hello world';
195+
const result = JupyterPositronRange.from(range, text);
196+
197+
assert.strictEqual(result.start.line, 0);
198+
assert.strictEqual(result.start.character, 0);
199+
assert.strictEqual(result.end.line, 0);
200+
assert.strictEqual(result.end.character, 5);
201+
});
202+
203+
test('Range with emoji', () => {
204+
const range = new vscode.Range(
205+
new vscode.Position(0, 0),
206+
new vscode.Position(0, 4)
207+
);
208+
const text = '😀😁';
209+
const result = JupyterPositronRange.from(range, text);
210+
211+
assert.strictEqual(result.start.line, 0);
212+
assert.strictEqual(result.start.character, 0);
213+
assert.strictEqual(result.end.line, 0);
214+
assert.strictEqual(result.end.character, 2, 'Should count 2 emojis as 2 code points');
215+
});
216+
217+
test('Multi-line range', () => {
218+
const range = new vscode.Range(
219+
new vscode.Position(1, 2),
220+
new vscode.Position(3, 4)
221+
);
222+
const text = 'test';
223+
const result = JupyterPositronRange.from(range, text);
224+
225+
assert.strictEqual(result.start.line, 1);
226+
assert.strictEqual(result.start.character, 2);
227+
assert.strictEqual(result.end.line, 3);
228+
assert.strictEqual(result.end.character, 4);
229+
});
230+
});
231+
232+
suite('JupyterPositronLocation', () => {
233+
234+
test('Location with file URI', () => {
235+
const uri = vscode.Uri.file('/path/to/file.txt');
236+
const range = new vscode.Range(
237+
new vscode.Position(0, 0),
238+
new vscode.Position(0, 5)
239+
);
240+
const location = new vscode.Location(uri, range);
241+
const text = 'hello';
242+
const result = JupyterPositronLocation.from(location, text);
243+
244+
assert.ok(result.uri.includes('file'));
245+
assert.ok(result.uri.includes('file.txt'));
246+
assert.strictEqual(result.range.start.line, 0);
247+
assert.strictEqual(result.range.start.character, 0);
248+
assert.strictEqual(result.range.end.line, 0);
249+
assert.strictEqual(result.range.end.character, 5);
250+
});
251+
252+
test('Location with emoji in text', () => {
253+
const uri = vscode.Uri.file('/test.txt');
254+
const range = new vscode.Range(
255+
new vscode.Position(0, 0),
256+
new vscode.Position(0, 3)
257+
);
258+
const location = new vscode.Location(uri, range);
259+
const text = '😀a';
260+
const result = JupyterPositronLocation.from(location, text);
261+
262+
assert.ok(result.uri.includes('test.txt'));
263+
assert.strictEqual(result.range.start.line, 0);
264+
assert.strictEqual(result.range.start.character, 0);
265+
assert.strictEqual(result.range.end.line, 0);
266+
assert.strictEqual(result.range.end.character, 2, 'Should count emoji + a as 2 code points');
267+
});
268+
});
269+
});

0 commit comments

Comments
 (0)