Skip to content

Commit 6f48130

Browse files
committed
test: add TabstopManager.parseSnippet unit tests
Cover plain text, simple stops ($1/$0/${0}), ordering (positives ascending with $0 last, mirror dedupe), placeholders incl. nested, choices, variables, escapes and multi-line offsets. Registered in UnitTestSuite. Also fix parseSnippet to preserve tab-stops nested inside a placeholder default (e.g. ${1:a ${2:b} c}), which were previously discarded.
1 parent f097e92 commit 6f48130

3 files changed

Lines changed: 166 additions & 0 deletions

File tree

src/editor/TabstopManager.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ define(function (require, exports, module) {
123123
phStart = out.length;
124124
out += sub.text;
125125
record(parseInt(placeholder[1], 10), phStart, out.length);
126+
// Preserve any tab-stops nested inside the placeholder default (e.g.
127+
// ${1:a ${2:b} c}), shifted into this snippet's coordinate space.
128+
for (var si = 0; si < sub.stops.length; si++) {
129+
record(sub.stops[si].number,
130+
phStart + sub.stops[si].start, phStart + sub.stops[si].end);
131+
}
126132
} else if (choice) {
127133
var first = choice[2].split(",")[0] || "",
128134
chStart = out.length;

test/UnitTestSuite.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ define(function (require, exports, module) {
126126
require("spec/Generic-integ-test");
127127
require("spec/spacing-auto-detect-integ-test");
128128
require("spec/LocalizationUtils-test");
129+
require("spec/TabstopManager-test");
129130
require("spec/ScrollTrackHandler-integ-test");
130131
// Integrated extension tests
131132
require("spec/Extn-RemoteFileAdapter-integ-test");

test/spec/TabstopManager-test.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it
7+
* under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
14+
* for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
18+
*
19+
*/
20+
21+
/*global describe, it, expect*/
22+
23+
define(function (require, exports, module) {
24+
const TabstopManager = require("editor/TabstopManager");
25+
26+
describe("unit:TabstopManager", function () {
27+
28+
// Convenience: stops as [number, start, end] tuples for terse comparisons.
29+
function tuples(stops) {
30+
return stops.map(function (s) {
31+
return [s.number, s.start, s.end];
32+
});
33+
}
34+
35+
describe("parseSnippet - plain text", function () {
36+
it("should leave text without tab-stops untouched and report no stops", function () {
37+
const parsed = TabstopManager.parseSnippet("console.log");
38+
expect(parsed.text).toBe("console.log");
39+
expect(parsed.stops).toEqual([]);
40+
});
41+
42+
it("should handle an empty snippet", function () {
43+
const parsed = TabstopManager.parseSnippet("");
44+
expect(parsed.text).toBe("");
45+
expect(parsed.stops).toEqual([]);
46+
});
47+
});
48+
49+
describe("parseSnippet - simple tab-stops", function () {
50+
it("should strip a trailing $1 and record an empty stop at its offset", function () {
51+
const parsed = TabstopManager.parseSnippet('import { getFromIndex$1 } from "./db";');
52+
expect(parsed.text).toBe('import { getFromIndex } from "./db";');
53+
// "$1" sits right after "getFromIndex" (offset 21)
54+
expect(tuples(parsed.stops)).toEqual([[1, 21, 21]]);
55+
});
56+
57+
it("should strip $0 and record it as the (only) stop", function () {
58+
const parsed = TabstopManager.parseSnippet("doThing()$0");
59+
expect(parsed.text).toBe("doThing()");
60+
expect(tuples(parsed.stops)).toEqual([[0, 9, 9]]);
61+
});
62+
63+
it("should support ${0} brace form", function () {
64+
const parsed = TabstopManager.parseSnippet("a${0}b");
65+
expect(parsed.text).toBe("ab");
66+
expect(tuples(parsed.stops)).toEqual([[0, 1, 1]]);
67+
});
68+
});
69+
70+
describe("parseSnippet - ordering", function () {
71+
it("should order positive stops ascending with $0 last", function () {
72+
const parsed = TabstopManager.parseSnippet("$2 $1 $0 $3");
73+
// text is " " (each stop is empty, separated by single spaces)
74+
expect(parsed.text).toBe(" ");
75+
expect(tuples(parsed.stops)).toEqual([
76+
[1, 1, 1],
77+
[2, 0, 0],
78+
[3, 3, 3],
79+
[0, 2, 2]
80+
]);
81+
});
82+
83+
it("should keep the first occurrence offset for a repeated (mirror) stop number", function () {
84+
const parsed = TabstopManager.parseSnippet("$1-$1");
85+
expect(parsed.text).toBe("-");
86+
// first $1 at offset 0, second occurrence ignored for placement
87+
expect(tuples(parsed.stops)).toEqual([[1, 0, 0]]);
88+
});
89+
});
90+
91+
describe("parseSnippet - placeholders", function () {
92+
it("should keep placeholder default text and span it as the stop range", function () {
93+
const parsed = TabstopManager.parseSnippet("connect(${1:host}, ${2:port})$0");
94+
expect(parsed.text).toBe("connect(host, port)");
95+
expect(tuples(parsed.stops)).toEqual([
96+
[1, 8, 12], // "host"
97+
[2, 14, 18], // "port"
98+
[0, 19, 19] // final caret at end
99+
]);
100+
});
101+
102+
it("should expand a nested placeholder", function () {
103+
const parsed = TabstopManager.parseSnippet("${1:a ${2:b} c}");
104+
expect(parsed.text).toBe("a b c");
105+
expect(tuples(parsed.stops)).toEqual([
106+
[1, 0, 5], // whole "a b c"
107+
[2, 2, 3] // inner "b"
108+
]);
109+
});
110+
});
111+
112+
describe("parseSnippet - choices", function () {
113+
it("should use the first choice as the inserted/selected text", function () {
114+
const parsed = TabstopManager.parseSnippet("type: ${1|number,string,boolean|}");
115+
expect(parsed.text).toBe("type: number");
116+
expect(tuples(parsed.stops)).toEqual([[1, 6, 12]]);
117+
});
118+
});
119+
120+
describe("parseSnippet - variables", function () {
121+
it("should drop an unresolved bare variable", function () {
122+
const parsed = TabstopManager.parseSnippet("name: $TM_FILENAME!");
123+
expect(parsed.text).toBe("name: !");
124+
expect(parsed.stops).toEqual([]);
125+
});
126+
127+
it("should drop an unresolved ${VAR} but keep its default", function () {
128+
const parsed = TabstopManager.parseSnippet("${TM_FILENAME:untitled}.txt");
129+
expect(parsed.text).toBe("untitled.txt");
130+
expect(parsed.stops).toEqual([]);
131+
});
132+
});
133+
134+
describe("parseSnippet - escapes", function () {
135+
it("should treat \\$ as a literal dollar, not a tab-stop", function () {
136+
const parsed = TabstopManager.parseSnippet("cost is \\$1 today");
137+
expect(parsed.text).toBe("cost is $1 today");
138+
expect(parsed.stops).toEqual([]);
139+
});
140+
141+
it("should unescape \\} and \\\\", function () {
142+
const parsed = TabstopManager.parseSnippet("a\\}b\\\\c");
143+
expect(parsed.text).toBe("a}b\\c");
144+
expect(parsed.stops).toEqual([]);
145+
});
146+
});
147+
148+
describe("parseSnippet - multi-line", function () {
149+
it("should preserve newlines and report offsets across them", function () {
150+
const parsed = TabstopManager.parseSnippet("if ($1) {\n $0\n}");
151+
expect(parsed.text).toBe("if () {\n \n}");
152+
expect(tuples(parsed.stops)).toEqual([
153+
[1, 4, 4], // inside the parens on line 1
154+
[0, 12, 12] // indented body on line 2
155+
]);
156+
});
157+
});
158+
});
159+
});

0 commit comments

Comments
 (0)