@quenty/nevermore-cli 4.49.0 → 4.50.1
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.
- package/CHANGELOG.md +12 -0
- package/dist/commands/tools-command/ci-post-test-results.d.ts.map +1 -1
- package/dist/commands/tools-command/ci-post-test-results.js +7 -3
- package/dist/commands/tools-command/ci-post-test-results.js.map +1 -1
- package/dist/utils/batch/changed-packages-utils.d.ts +8 -0
- package/dist/utils/batch/changed-packages-utils.d.ts.map +1 -1
- package/dist/utils/batch/changed-packages-utils.js +9 -11
- package/dist/utils/batch/changed-packages-utils.js.map +1 -1
- package/dist/utils/testing/batch-run.e2e.test.js +2 -2
- package/dist/utils/testing/batch-run.e2e.test.js.map +1 -1
- package/dist/utils/testing/parsers/annotation-workspace.d.ts +17 -0
- package/dist/utils/testing/parsers/annotation-workspace.d.ts.map +1 -0
- package/dist/utils/testing/parsers/annotation-workspace.js +47 -0
- package/dist/utils/testing/parsers/annotation-workspace.js.map +1 -0
- package/dist/utils/testing/parsers/index.d.ts +0 -1
- package/dist/utils/testing/parsers/index.d.ts.map +1 -1
- package/dist/utils/testing/parsers/index.js +0 -1
- package/dist/utils/testing/parsers/index.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts +14 -29
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.js +85 -100
- package/dist/utils/testing/parsers/jest-lua-parser.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.test.js +359 -56
- package/dist/utils/testing/parsers/jest-lua-parser.test.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts +23 -17
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.js +89 -54
- package/dist/utils/testing/parsers/roblox-path-resolver.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js +76 -36
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.d.ts.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.js +5 -6
- package/dist/utils/testing/runner/combined-project-generator.js.map +1 -1
- package/dist/utils/testing/runner/test-project-slug.d.ts +11 -0
- package/dist/utils/testing/runner/test-project-slug.d.ts.map +1 -0
- package/dist/utils/testing/runner/test-project-slug.js +11 -0
- package/dist/utils/testing/runner/test-project-slug.js.map +1 -0
- package/package.json +6 -6
- package/src/commands/tools-command/ci-post-test-results.ts +7 -2
- package/src/utils/batch/changed-packages-utils.ts +21 -12
- package/src/utils/testing/batch-run.e2e.test.ts +6 -2
- package/src/utils/testing/parsers/annotation-workspace.ts +74 -0
- package/src/utils/testing/parsers/index.ts +0 -1
- package/src/utils/testing/parsers/jest-lua-parser.test.ts +433 -65
- package/src/utils/testing/parsers/jest-lua-parser.ts +131 -109
- package/src/utils/testing/parsers/roblox-path-resolver.test.ts +144 -61
- package/src/utils/testing/parsers/roblox-path-resolver.ts +117 -56
- package/src/utils/testing/runner/combined-project-generator.ts +9 -13
- package/src/utils/testing/runner/test-project-slug.ts +18 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,76 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parser for Jest-lua verbose output (`verbose=true, ci=true`).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* ● ObservableList > add and remove > should track count
|
|
9
|
-
*
|
|
10
|
-
* expect(received).toEqual(expected)
|
|
11
|
-
*
|
|
12
|
-
* Expected: 3
|
|
13
|
-
* Received: 2
|
|
14
|
-
*
|
|
15
|
-
* at ServerScriptService.observablecollection.Shared.ObservableList.spec:45
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* 2. **Runtime errors** — `Stack Begin`/`Stack End` blocks:
|
|
19
|
-
* ```
|
|
20
|
-
* Error: attempt to index nil with "Connect"
|
|
21
|
-
* Stack Begin
|
|
22
|
-
* Script 'ServerScriptService.maid.Shared.Maid.spec', Line 23
|
|
23
|
-
* Stack End
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* The parser is intentionally lenient — it extracts what it can, skips what
|
|
27
|
-
* it can't, and never crashes on unexpected input.
|
|
4
|
+
* Emits one diagnostic per `●` failure block and per `Stack Begin`/`Stack End`
|
|
5
|
+
* runtime error, at the innermost frame that resolves to a repo file,
|
|
6
|
+
* preferring the package under test. Diagnostics with no resolvable frame
|
|
7
|
+
* carry no file. Unexpected input is skipped, never thrown on.
|
|
28
8
|
*/
|
|
29
9
|
|
|
30
10
|
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
31
12
|
import { type Diagnostic } from '@quenty/cli-output-helpers/reporting';
|
|
32
13
|
import { OutputHelper } from '@quenty/cli-output-helpers';
|
|
33
14
|
import type { SourcemapResolver } from '../../sourcemap/index.js';
|
|
34
|
-
import {
|
|
15
|
+
import type { AnnotationWorkspace } from './annotation-workspace.js';
|
|
16
|
+
import { tryResolveRobloxSourceFile } from './roblox-path-resolver.js';
|
|
35
17
|
|
|
36
|
-
/**
|
|
37
|
-
const
|
|
18
|
+
/** `at ServerScriptService.pkg.Path.spec:45`, line optional */
|
|
19
|
+
const AT_FRAME_PATTERN = /^\s+at\s+(\S+?)(?::(\d+))?\s*$/;
|
|
38
20
|
|
|
39
|
-
/**
|
|
40
|
-
const
|
|
21
|
+
/** `ServerScriptService.pkg.Path:1263 function requireModule`, any service root */
|
|
22
|
+
const BARE_FRAME_PATTERN =
|
|
23
|
+
/^\s+(\[string "[^"]+"\]|[A-Za-z_][\w-]*(?:\.[^\s:"]+)+):(\d+)(?:\s+function\s+\S+)?\s*$/;
|
|
41
24
|
|
|
42
|
-
/**
|
|
43
|
-
const
|
|
25
|
+
/** `Script '[string "ServerScriptService.pkg.Path"]', Line 23` */
|
|
26
|
+
const SCRIPT_FRAME_PATTERN = /Script '(.+?)', Line (\d+)/;
|
|
44
27
|
|
|
45
|
-
/**
|
|
28
|
+
/** `<chunk>:214:` location prefixes inside error messages */
|
|
29
|
+
const MESSAGE_LOCATION_PATTERN =
|
|
30
|
+
/(\[string "[^"]+"\]|ServerScriptService\.[^\s:"]+):(\d+):/g;
|
|
31
|
+
|
|
32
|
+
/** `● TestSuite › nested › test name` */
|
|
46
33
|
const FAILURE_HEADER_PATTERN = /^\s*●\s+(.+)$/;
|
|
47
34
|
|
|
35
|
+
const TITLE_SEPARATOR_PATTERN = / [›>] /;
|
|
36
|
+
|
|
48
37
|
const enum State {
|
|
49
38
|
IDLE,
|
|
50
39
|
FAILURE_BLOCK,
|
|
51
40
|
STACK_BLOCK,
|
|
52
41
|
}
|
|
53
42
|
|
|
43
|
+
interface Frame {
|
|
44
|
+
instancePath: string;
|
|
45
|
+
line?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
54
48
|
interface FailureContext {
|
|
55
49
|
title: string;
|
|
56
50
|
messageLines: string[];
|
|
57
|
-
|
|
58
|
-
line?: number;
|
|
51
|
+
frames: Frame[];
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
interface StackContext {
|
|
62
55
|
errorMessage: string;
|
|
63
|
-
|
|
56
|
+
frames: Frame[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ResolvedLocation {
|
|
60
|
+
file: string;
|
|
64
61
|
line?: number;
|
|
62
|
+
isPackageUnderTest: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface JestLuaParseOptions {
|
|
66
|
+
/** npm name of the package under test, e.g. `@quenty/saveslot`. */
|
|
67
|
+
packageName: string;
|
|
68
|
+
sourcemapResolver?: SourcemapResolver;
|
|
69
|
+
workspace?: AnnotationWorkspace;
|
|
70
|
+
/** Repo root that holds the workspace. Defaults to cwd. */
|
|
71
|
+
repoRoot?: string;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
/**
|
|
68
|
-
* Search a `.spec.lua` file for an `it()` call matching `testName`.
|
|
69
|
-
* Returns the 1-indexed line number, or `undefined` if not found.
|
|
70
|
-
*/
|
|
71
74
|
function _findTestLineInFile(
|
|
72
75
|
filePath: string,
|
|
73
|
-
|
|
76
|
+
title: string
|
|
74
77
|
): number | undefined {
|
|
75
78
|
let content: string;
|
|
76
79
|
try {
|
|
@@ -80,31 +83,67 @@ function _findTestLineInFile(
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
const lines = content.split('\n');
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
}
|
|
86
|
+
const names = title.split(TITLE_SEPARATOR_PATTERN).map((name) => name.trim());
|
|
87
|
+
for (const name of names.reverse()) {
|
|
88
|
+
const index = lines.findIndex(
|
|
89
|
+
(line) => line.includes(`("${name}"`) || line.includes(`('${name}'`)
|
|
90
|
+
);
|
|
91
|
+
if (index !== -1) return index + 1;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
return undefined;
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
function _parseFrameLine(rawLine: string): Frame | undefined {
|
|
98
|
+
const match =
|
|
99
|
+
rawLine.match(BARE_FRAME_PATTERN) ?? rawLine.match(AT_FRAME_PATTERN);
|
|
100
|
+
if (!match) return undefined;
|
|
101
|
+
return {
|
|
102
|
+
instancePath: match[1],
|
|
103
|
+
line: match[2] ? parseInt(match[2], 10) : undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function _parseMessageLocations(message: string): Frame[] {
|
|
108
|
+
return [...message.matchAll(MESSAGE_LOCATION_PATTERN)].map((match) => ({
|
|
109
|
+
instancePath: match[1],
|
|
110
|
+
line: parseInt(match[2], 10),
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _resolveLocation(
|
|
115
|
+
frames: Frame[],
|
|
116
|
+
options: JestLuaParseOptions
|
|
117
|
+
): ResolvedLocation | undefined {
|
|
118
|
+
let fallback: ResolvedLocation | undefined;
|
|
119
|
+
|
|
120
|
+
for (const frame of frames) {
|
|
121
|
+
const resolved = tryResolveRobloxSourceFile(frame.instancePath, options);
|
|
122
|
+
if (!resolved) continue;
|
|
123
|
+
|
|
124
|
+
const location = { ...resolved, line: frame.line };
|
|
125
|
+
if (resolved.isPackageUnderTest) {
|
|
126
|
+
return location;
|
|
127
|
+
}
|
|
128
|
+
fallback ??= location;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return fallback;
|
|
132
|
+
}
|
|
133
|
+
|
|
95
134
|
/**
|
|
96
135
|
* Parse Jest-lua verbose output into `Diagnostic[]`.
|
|
97
136
|
*
|
|
98
137
|
* @param raw - Raw test output (may include ANSI codes)
|
|
99
|
-
* @param options.packageName - Package name for fallback path resolution
|
|
100
138
|
*/
|
|
101
139
|
export function parseJestLuaOutput(
|
|
102
140
|
raw: string,
|
|
103
|
-
options:
|
|
141
|
+
options: JestLuaParseOptions
|
|
104
142
|
): Diagnostic[] {
|
|
105
143
|
const diagnostics: Diagnostic[] = [];
|
|
106
144
|
const clean = OutputHelper.stripAnsi(raw);
|
|
107
145
|
const lines = clean.split('\n');
|
|
146
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
108
147
|
|
|
109
148
|
let state: State = State.IDLE;
|
|
110
149
|
let failureCtx: FailureContext | undefined;
|
|
@@ -113,32 +152,19 @@ export function parseJestLuaOutput(
|
|
|
113
152
|
|
|
114
153
|
function _emitFailure(ctx: FailureContext): void {
|
|
115
154
|
const message = ctx.messageLines.join('\n').trim();
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (ctx.file) {
|
|
125
|
-
file = resolveRobloxTestPath(ctx.file, options.sourcemapResolver);
|
|
126
|
-
|
|
127
|
-
if (ctx.line) {
|
|
128
|
-
line = ctx.line;
|
|
129
|
-
} else {
|
|
130
|
-
const segments = ctx.title.split(' > ');
|
|
131
|
-
const testName = segments[segments.length - 1].trim();
|
|
132
|
-
line = _findTestLineInFile(file, testName) ?? 1;
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
file = `src/${options.packageName}/src`;
|
|
136
|
-
line = 1;
|
|
155
|
+
const location = _resolveLocation(
|
|
156
|
+
[...ctx.frames, ..._parseMessageLocations(message)],
|
|
157
|
+
options
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
let line = location?.line;
|
|
161
|
+
if (location?.isPackageUnderTest && line === undefined) {
|
|
162
|
+
line = _findTestLineInFile(path.join(repoRoot, location.file), ctx.title);
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
diagnostics.push({
|
|
140
|
-
file,
|
|
141
|
-
line,
|
|
166
|
+
...(location ? { file: location.file } : {}),
|
|
167
|
+
...(line !== undefined ? { line } : {}),
|
|
142
168
|
severity: 'error',
|
|
143
169
|
title: ctx.title,
|
|
144
170
|
message: message || 'Test failed',
|
|
@@ -147,14 +173,13 @@ export function parseJestLuaOutput(
|
|
|
147
173
|
}
|
|
148
174
|
|
|
149
175
|
function _emitStack(ctx: StackContext): void {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
const location = _resolveLocation(
|
|
177
|
+
[...ctx.frames, ..._parseMessageLocations(ctx.errorMessage)],
|
|
178
|
+
options
|
|
179
|
+
);
|
|
154
180
|
|
|
155
181
|
diagnostics.push({
|
|
156
|
-
file,
|
|
157
|
-
line,
|
|
182
|
+
...(location ? { file: location.file, line: location.line ?? 1 } : {}),
|
|
158
183
|
severity: 'error',
|
|
159
184
|
title: 'Runtime error',
|
|
160
185
|
message: ctx.errorMessage || 'Unknown runtime error',
|
|
@@ -162,7 +187,9 @@ export function parseJestLuaOutput(
|
|
|
162
187
|
});
|
|
163
188
|
}
|
|
164
189
|
|
|
165
|
-
for (
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
const rawLine = lines[i];
|
|
192
|
+
|
|
166
193
|
switch (state) {
|
|
167
194
|
case State.IDLE: {
|
|
168
195
|
const failureMatch = rawLine.match(FAILURE_HEADER_PATTERN);
|
|
@@ -170,6 +197,7 @@ export function parseJestLuaOutput(
|
|
|
170
197
|
failureCtx = {
|
|
171
198
|
title: failureMatch[1].trim(),
|
|
172
199
|
messageLines: [],
|
|
200
|
+
frames: [],
|
|
173
201
|
};
|
|
174
202
|
state = State.FAILURE_BLOCK;
|
|
175
203
|
break;
|
|
@@ -178,6 +206,7 @@ export function parseJestLuaOutput(
|
|
|
178
206
|
if (rawLine.trim() === 'Stack Begin') {
|
|
179
207
|
stackCtx = {
|
|
180
208
|
errorMessage: lastNonEmptyLine,
|
|
209
|
+
frames: [],
|
|
181
210
|
};
|
|
182
211
|
state = State.STACK_BLOCK;
|
|
183
212
|
break;
|
|
@@ -195,32 +224,9 @@ export function parseJestLuaOutput(
|
|
|
195
224
|
break;
|
|
196
225
|
}
|
|
197
226
|
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
failureCtx.
|
|
201
|
-
failureCtx.line = parseInt(atMatch[2], 10);
|
|
202
|
-
_emitFailure(failureCtx);
|
|
203
|
-
failureCtx = undefined;
|
|
204
|
-
state = State.IDLE;
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const atNoLineMatch = rawLine.match(AT_LOCATION_NO_LINE_PATTERN);
|
|
209
|
-
if (atNoLineMatch) {
|
|
210
|
-
failureCtx.file = atNoLineMatch[1];
|
|
211
|
-
_emitFailure(failureCtx);
|
|
212
|
-
failureCtx = undefined;
|
|
213
|
-
state = State.IDLE;
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const nextFailure = rawLine.match(FAILURE_HEADER_PATTERN);
|
|
218
|
-
if (nextFailure) {
|
|
219
|
-
_emitFailure(failureCtx);
|
|
220
|
-
failureCtx = {
|
|
221
|
-
title: nextFailure[1].trim(),
|
|
222
|
-
messageLines: [],
|
|
223
|
-
};
|
|
227
|
+
const frame = _parseFrameLine(rawLine);
|
|
228
|
+
if (frame) {
|
|
229
|
+
failureCtx.frames.push(frame);
|
|
224
230
|
break;
|
|
225
231
|
}
|
|
226
232
|
|
|
@@ -233,13 +239,27 @@ export function parseJestLuaOutput(
|
|
|
233
239
|
failureCtx.messageLines.length - 1
|
|
234
240
|
].trim()
|
|
235
241
|
: failureCtx.title,
|
|
242
|
+
frames: [],
|
|
236
243
|
};
|
|
237
244
|
failureCtx = undefined;
|
|
238
245
|
state = State.STACK_BLOCK;
|
|
239
246
|
break;
|
|
240
247
|
}
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
const endsBlock =
|
|
250
|
+
FAILURE_HEADER_PATTERN.test(rawLine) ||
|
|
251
|
+
(failureCtx.frames.length > 0 && rawLine.trim() !== '');
|
|
252
|
+
if (endsBlock) {
|
|
253
|
+
_emitFailure(failureCtx);
|
|
254
|
+
failureCtx = undefined;
|
|
255
|
+
state = State.IDLE;
|
|
256
|
+
i--;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (failureCtx.frames.length === 0) {
|
|
261
|
+
failureCtx.messageLines.push(rawLine);
|
|
262
|
+
}
|
|
243
263
|
break;
|
|
244
264
|
}
|
|
245
265
|
|
|
@@ -249,10 +269,12 @@ export function parseJestLuaOutput(
|
|
|
249
269
|
break;
|
|
250
270
|
}
|
|
251
271
|
|
|
252
|
-
const scriptMatch = rawLine.match(
|
|
272
|
+
const scriptMatch = rawLine.match(SCRIPT_FRAME_PATTERN);
|
|
253
273
|
if (scriptMatch) {
|
|
254
|
-
stackCtx.
|
|
255
|
-
|
|
274
|
+
stackCtx.frames.push({
|
|
275
|
+
instancePath: scriptMatch[1],
|
|
276
|
+
line: parseInt(scriptMatch[2], 10),
|
|
277
|
+
});
|
|
256
278
|
break;
|
|
257
279
|
}
|
|
258
280
|
|
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
1
|
+
import { beforeAll, describe, it, expect } from 'vitest';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { tryResolveRobloxSourceFile } from './roblox-path-resolver.js';
|
|
3
4
|
import { SourcemapResolver } from '../../sourcemap/index.js';
|
|
4
5
|
import type { SourcemapNode } from '../../sourcemap/index.js';
|
|
6
|
+
import {
|
|
7
|
+
type AnnotationWorkspace,
|
|
8
|
+
tryLoadAnnotationWorkspaceAsync,
|
|
9
|
+
} from './annotation-workspace.js';
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
const REPO_ROOT = fileURLToPath(new URL('../../../../../../', import.meta.url));
|
|
12
|
+
|
|
13
|
+
let workspace: AnnotationWorkspace | undefined;
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
workspace = await tryLoadAnnotationWorkspaceAsync(REPO_ROOT);
|
|
16
|
+
}, 30000);
|
|
17
|
+
|
|
18
|
+
function resolveFile(
|
|
19
|
+
instancePath: string,
|
|
20
|
+
packageName?: string
|
|
21
|
+
): string | undefined {
|
|
22
|
+
return tryResolveRobloxSourceFile(instancePath, {
|
|
23
|
+
repoRoot: REPO_ROOT,
|
|
24
|
+
workspace,
|
|
25
|
+
packageName,
|
|
26
|
+
})?.file;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('tryResolveRobloxSourceFile', () => {
|
|
7
30
|
it('resolves a standard ServerScriptService path', () => {
|
|
8
31
|
expect(
|
|
9
|
-
|
|
32
|
+
resolveFile(
|
|
10
33
|
'ServerScriptService.observablecollection.Shared.ObservableList.spec'
|
|
11
34
|
)
|
|
12
35
|
).toBe('src/observablecollection/src/Shared/ObservableList.spec.lua');
|
|
@@ -14,48 +37,121 @@ describe('resolveRobloxTestPath', () => {
|
|
|
14
37
|
|
|
15
38
|
it('resolves a nested subdirectory path', () => {
|
|
16
39
|
expect(
|
|
17
|
-
|
|
18
|
-
|
|
40
|
+
resolveFile(
|
|
41
|
+
'ServerScriptService.observablecollection.Shared.SortedList.ObservableSortedList'
|
|
42
|
+
)
|
|
43
|
+
).toBe(
|
|
44
|
+
'src/observablecollection/src/Shared/SortedList/ObservableSortedList.lua'
|
|
45
|
+
);
|
|
19
46
|
});
|
|
20
47
|
|
|
21
|
-
it('
|
|
48
|
+
it('handles missing ServerScriptService prefix', () => {
|
|
49
|
+
expect(resolveFile('maid.Shared.MaidTaskUtils.spec')).toBe(
|
|
50
|
+
'src/maid/src/Shared/MaidTaskUtils.spec.lua'
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('unwraps a chunk-name wrapper', () => {
|
|
22
55
|
expect(
|
|
23
|
-
|
|
24
|
-
'ServerScriptService.
|
|
56
|
+
resolveFile(
|
|
57
|
+
'[string "ServerScriptService.saveslot.Server.HasSaveSlotsDataStore"]'
|
|
25
58
|
)
|
|
26
|
-
).toBe('src/
|
|
59
|
+
).toBe('src/saveslot/src/Server/HasSaveSlotsDataStore.lua');
|
|
27
60
|
});
|
|
28
61
|
|
|
29
|
-
it('
|
|
62
|
+
it('maps a nested dependency copy to its own package', () => {
|
|
30
63
|
expect(
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
resolveFile(
|
|
65
|
+
'ServerScriptService.saveslot.node_modules.@quenty.adorneedata.node_modules.@quenty.attributeutils.node_modules.@quenty.baseobject.node_modules.@quenty.maid.Shared.Maid'
|
|
66
|
+
)
|
|
67
|
+
).toBe('src/maid/src/Shared/Maid.lua');
|
|
33
68
|
});
|
|
34
69
|
|
|
35
|
-
it('
|
|
70
|
+
it('maps a dependency whose folder differs from its npm name', () => {
|
|
36
71
|
expect(
|
|
37
|
-
|
|
38
|
-
'ServerScriptService.
|
|
72
|
+
resolveFile(
|
|
73
|
+
'[string "ServerScriptService.gameproduct.node_modules.@quenty.playermock.Shared.PlayerMock"]'
|
|
39
74
|
)
|
|
40
|
-
).toBe('src/
|
|
75
|
+
).toBe('src/player-mock/src/Shared/PlayerMock.lua');
|
|
41
76
|
});
|
|
42
77
|
|
|
43
|
-
it('
|
|
44
|
-
expect(
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
it('resolves a package root module to its init file', () => {
|
|
79
|
+
expect(
|
|
80
|
+
resolveFile(
|
|
81
|
+
'[string "ServerScriptService.studiobridge.node_modules.@quenty.nevermore-test-runner.node_modules.@quenty.loader"]'
|
|
82
|
+
)
|
|
83
|
+
).toBe('src/loader/src/init.lua');
|
|
47
84
|
});
|
|
48
85
|
|
|
49
|
-
it('
|
|
50
|
-
expect(
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
it('maps the test slug of the package under test to its folder', () => {
|
|
87
|
+
expect(
|
|
88
|
+
tryResolveRobloxSourceFile(
|
|
89
|
+
'ServerScriptService.gameproduct.Server.Cmdr.GameProductCmdrService',
|
|
90
|
+
{
|
|
91
|
+
repoRoot: REPO_ROOT,
|
|
92
|
+
workspace,
|
|
93
|
+
packageName: '@quenty/gameproductservice',
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
).toEqual({
|
|
97
|
+
file: 'src/gameproductservice/src/Server/Cmdr/GameProductCmdrService.lua',
|
|
98
|
+
isPackageUnderTest: true,
|
|
99
|
+
});
|
|
100
|
+
expect(
|
|
101
|
+
resolveFile(
|
|
102
|
+
'ServerScriptService.playermock.Shared.PlayerMock',
|
|
103
|
+
'@quenty/playermock'
|
|
104
|
+
)
|
|
105
|
+
).toBe('src/player-mock/src/Shared/PlayerMock.lua');
|
|
53
106
|
});
|
|
54
107
|
|
|
55
|
-
it('
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
108
|
+
it('resolves a package outside src/ by its workspace directory', () => {
|
|
109
|
+
expect(
|
|
110
|
+
resolveFile(
|
|
111
|
+
'ServerScriptService.studiobridge.commands.console.exec.execute.spec',
|
|
112
|
+
'@quenty/studio-bridge'
|
|
113
|
+
)
|
|
114
|
+
).toBe('tools/studio-bridge/src/commands/console/exec/execute.spec.lua');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('tries the script file names Rojo strips', () => {
|
|
118
|
+
expect(
|
|
119
|
+
resolveFile('ServerScriptService.x.node_modules.@quenty.preview-plugin')
|
|
120
|
+
).toBe('plugins/preview-plugin/src/init.server.lua');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('flags dependency frames as not under test', () => {
|
|
124
|
+
expect(
|
|
125
|
+
tryResolveRobloxSourceFile(
|
|
126
|
+
'ServerScriptService.saveslot.node_modules.@quenty.binder.Shared.Binder',
|
|
127
|
+
{ repoRoot: REPO_ROOT, workspace, packageName: '@quenty/saveslot' }
|
|
128
|
+
)
|
|
129
|
+
).toEqual({
|
|
130
|
+
file: 'src/binder/src/Shared/Binder.lua',
|
|
131
|
+
isPackageUnderTest: false,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('does not remap a sibling package frame into the package under test', () => {
|
|
136
|
+
expect(
|
|
137
|
+
resolveFile(
|
|
138
|
+
'ServerScriptService.othersibling.Server.SaveSlotService',
|
|
139
|
+
'@quenty/saveslot'
|
|
140
|
+
)
|
|
141
|
+
).toBeUndefined();
|
|
142
|
+
expect(
|
|
143
|
+
resolveFile('ServerScriptService.Script', '@quenty/saveslot')
|
|
144
|
+
).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('rejects vendored, truncated and missing paths', () => {
|
|
148
|
+
for (const instancePath of [
|
|
149
|
+
'ServerScriptService.studiobridge.node_modules.@quenty.nevermore-test-runner.node_modules.@quentystudios.Jest._Index.jsdotlua_promise@3.5.2.promise',
|
|
150
|
+
'[string "ServerScriptService.gameproduct.node_modules.@quenty.playermock.node_modules.@quenty.servic..."]',
|
|
151
|
+
'ServerScriptService.unknownpkg.Shared.Foo',
|
|
152
|
+
]) {
|
|
153
|
+
expect(resolveFile(instancePath)).toBeUndefined();
|
|
154
|
+
}
|
|
59
155
|
});
|
|
60
156
|
|
|
61
157
|
describe('with sourcemap resolver', () => {
|
|
@@ -66,26 +162,19 @@ describe('resolveRobloxTestPath', () => {
|
|
|
66
162
|
className: 'DataModel',
|
|
67
163
|
children: [
|
|
68
164
|
{
|
|
69
|
-
name: '
|
|
165
|
+
name: 'playermock',
|
|
70
166
|
className: 'Folder',
|
|
71
|
-
filePaths: [`${repoRoot}/src/
|
|
167
|
+
filePaths: [`${repoRoot}/src/player-mock/default.project.json`],
|
|
72
168
|
children: [
|
|
73
169
|
{
|
|
74
170
|
name: 'Shared',
|
|
75
171
|
className: 'Folder',
|
|
76
172
|
children: [
|
|
77
173
|
{
|
|
78
|
-
name: '
|
|
174
|
+
name: 'PlayerMock',
|
|
79
175
|
className: 'ModuleScript',
|
|
80
|
-
filePaths: [
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
name: 'MyModule.spec',
|
|
84
|
-
className: 'ModuleScript',
|
|
85
|
-
filePaths: [
|
|
86
|
-
`${repoRoot}/src/mypkg/src/Shared/MyModule.spec.lua`,
|
|
87
|
-
],
|
|
88
|
-
},
|
|
176
|
+
filePaths: [
|
|
177
|
+
`${repoRoot}/src/player-mock/src/Shared/PlayerMock.lua`,
|
|
89
178
|
],
|
|
90
179
|
},
|
|
91
180
|
],
|
|
@@ -97,31 +186,25 @@ describe('resolveRobloxTestPath', () => {
|
|
|
97
186
|
|
|
98
187
|
const resolver = SourcemapResolver.fromSourcemap(sourcemap, repoRoot);
|
|
99
188
|
|
|
100
|
-
it('
|
|
189
|
+
it('looks up dependency copies under their Rojo project name', () => {
|
|
101
190
|
expect(
|
|
102
|
-
|
|
103
|
-
'ServerScriptService.
|
|
104
|
-
resolver
|
|
105
|
-
)
|
|
106
|
-
).toBe('src/
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('falls back to heuristic when sourcemap has no mapping', () => {
|
|
110
|
-
expect(
|
|
111
|
-
resolveRobloxTestPath(
|
|
112
|
-
'ServerScriptService.unknownpkg.Shared.Foo.spec',
|
|
113
|
-
resolver
|
|
114
|
-
)
|
|
115
|
-
).toBe('src/unknownpkg/src/Shared/Foo.spec.lua');
|
|
191
|
+
tryResolveRobloxSourceFile(
|
|
192
|
+
'ServerScriptService.gameproduct.node_modules.@quenty.playermock.Shared.PlayerMock',
|
|
193
|
+
{ repoRoot, sourcemapResolver: resolver }
|
|
194
|
+
)?.file
|
|
195
|
+
).toBe('src/player-mock/src/Shared/PlayerMock.lua');
|
|
116
196
|
});
|
|
117
197
|
|
|
118
|
-
it('
|
|
198
|
+
it('returns undefined when neither sourcemap nor repo has the file', () => {
|
|
119
199
|
expect(
|
|
120
|
-
|
|
121
|
-
'ServerScriptService.
|
|
122
|
-
|
|
200
|
+
tryResolveRobloxSourceFile(
|
|
201
|
+
'ServerScriptService.unknownpkg.Shared.Foo',
|
|
202
|
+
{
|
|
203
|
+
repoRoot,
|
|
204
|
+
sourcemapResolver: resolver,
|
|
205
|
+
}
|
|
123
206
|
)
|
|
124
|
-
).
|
|
207
|
+
).toBeUndefined();
|
|
125
208
|
});
|
|
126
209
|
});
|
|
127
210
|
});
|