reslop 0.2.5 → 0.2.7
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 +22 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +72 -19
- package/lib/api.js +1 -1
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +61 -75
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +357 -0
- package/lib/dashboard/npm.js +290 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +79 -91
- package/lib/find.js +61 -0
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +148 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +161 -214
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +75 -83
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +412 -347
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +496 -0
- package/lib/render/dash-table.js +295 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +60 -86
- package/lib/render/frame.js +82 -117
- package/lib/render/notes.js +150 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/packages.js +93 -0
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +77 -172
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +355 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +69 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +189 -65
- package/lib/session/branches.js +164 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +298 -352
- package/lib/session/compose.js +572 -715
- package/lib/session/dashboard.js +530 -0
- package/lib/session/dispatch.js +197 -171
- package/lib/session/files-pane.js +92 -45
- package/lib/session/find.js +94 -0
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +420 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +4 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +30 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +277 -236
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +59 -80
- package/lib/session/watch.js +201 -212
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +95 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/workspace.js +166 -0
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/report-test.js
CHANGED
|
@@ -6,7 +6,7 @@ const { isPassingTest } = npmCommands;
|
|
|
6
6
|
const utilities = require('./utilities.js');
|
|
7
7
|
const { unquote } = utilities;
|
|
8
8
|
|
|
9
|
-
const { summary, problem, report, userTrace } = model;
|
|
9
|
+
const { summary, problem, report, userTrace, errorKey } = model;
|
|
10
10
|
|
|
11
11
|
const KNOWN = [
|
|
12
12
|
'message',
|
|
@@ -22,8 +22,38 @@ const KNOWN = [
|
|
|
22
22
|
'location',
|
|
23
23
|
'severity',
|
|
24
24
|
'type',
|
|
25
|
+
'at',
|
|
26
|
+
'found',
|
|
27
|
+
'wanted',
|
|
28
|
+
'got',
|
|
29
|
+
'expect',
|
|
25
30
|
];
|
|
26
31
|
|
|
32
|
+
const HIDDEN = ['generatedMessage', 'diff'];
|
|
33
|
+
|
|
34
|
+
const ERROR_HEAD = /^([\w.]*Error)(?: \[([^\]]+)\])?:\s*/;
|
|
35
|
+
|
|
36
|
+
const exceptionFields = (lines) => {
|
|
37
|
+
const rows = lines.map((line) => line.replace(/^\s*# ?/, ''));
|
|
38
|
+
const header = rows.findIndex((line) => ERROR_HEAD.test(line.trim()));
|
|
39
|
+
const frame = rows.findIndex((line) => /^\s*at\s/.test(line));
|
|
40
|
+
const start = header < 0 ? 0 : header;
|
|
41
|
+
const end = frame < 0 ? rows.length : frame;
|
|
42
|
+
const text = rows
|
|
43
|
+
.slice(start, end)
|
|
44
|
+
.map((line) => line.trim())
|
|
45
|
+
.join('\n');
|
|
46
|
+
const match = ERROR_HEAD.exec(text);
|
|
47
|
+
const message = match ? text.slice(match[0].length).trim() : text.trim();
|
|
48
|
+
const stack = frame < 0 ? '' : rows.slice(frame).join('\n');
|
|
49
|
+
return [
|
|
50
|
+
['message', message],
|
|
51
|
+
['name', match ? match[1] : ''],
|
|
52
|
+
['code', match ? match[2] || '' : ''],
|
|
53
|
+
['stack', stack],
|
|
54
|
+
];
|
|
55
|
+
};
|
|
56
|
+
|
|
27
57
|
const show = (value) => {
|
|
28
58
|
if (value === null || value === undefined) return '';
|
|
29
59
|
if (typeof value === 'string') return value;
|
|
@@ -36,7 +66,7 @@ const show = (value) => {
|
|
|
36
66
|
const extraFrom = (fields) => {
|
|
37
67
|
const extra = [];
|
|
38
68
|
for (const field of fields) {
|
|
39
|
-
if (KNOWN.includes(field[0])) continue;
|
|
69
|
+
if (KNOWN.includes(field[0]) || HIDDEN.includes(field[0])) continue;
|
|
40
70
|
const value = show(field[1]);
|
|
41
71
|
if (!value) continue;
|
|
42
72
|
const shown = value.length > 500 ? `${value.slice(0, 500)}…` : value;
|
|
@@ -45,30 +75,70 @@ const extraFrom = (fields) => {
|
|
|
45
75
|
return extra;
|
|
46
76
|
};
|
|
47
77
|
|
|
48
|
-
const
|
|
49
|
-
const map = new Map();
|
|
50
|
-
for (const field of fields) map.set(field[0], field[1]);
|
|
51
|
-
return map;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const problemFromFields = (name, fields, root) => {
|
|
55
|
-
const map = fieldMap(fields);
|
|
56
|
-
const message = show(map.get('error') || map.get('message'));
|
|
78
|
+
const problemFromFields = (name, fields, root, fallback = '') => {
|
|
79
|
+
const map = new Map(fields);
|
|
57
80
|
const stack = show(map.get('stack'));
|
|
58
|
-
const
|
|
59
|
-
const
|
|
81
|
+
const parsed = new Map(exceptionFields(stack.split(/\r?\n/)));
|
|
82
|
+
const text = show(
|
|
83
|
+
map.get('error') || map.get('message') || parsed.get('message'),
|
|
84
|
+
);
|
|
85
|
+
const header = ERROR_HEAD.exec(text);
|
|
86
|
+
const message = header ? text.slice(header[0].length).trim() : text;
|
|
87
|
+
const location = show(map.get('at') || map.get('location'));
|
|
60
88
|
const duration = show(map.get('duration_ms') || map.get('duration'));
|
|
61
|
-
|
|
89
|
+
const details = {
|
|
62
90
|
name,
|
|
63
91
|
message,
|
|
64
|
-
trace,
|
|
65
92
|
duration,
|
|
66
|
-
code: show(map.get('code')),
|
|
93
|
+
code: show(map.get('code') || (header && header[2]) || parsed.get('code')),
|
|
67
94
|
operator: show(map.get('operator')),
|
|
68
|
-
expected: show(
|
|
69
|
-
|
|
95
|
+
expected: show(
|
|
96
|
+
map.get('expected') ?? map.get('wanted') ?? map.get('expect'),
|
|
97
|
+
),
|
|
98
|
+
actual: show(map.get('actual') ?? map.get('found') ?? map.get('got')),
|
|
70
99
|
extra: extraFrom(fields),
|
|
71
|
-
}
|
|
100
|
+
};
|
|
101
|
+
const type = show(
|
|
102
|
+
map.get('name') || (header && header[1]) || parsed.get('name'),
|
|
103
|
+
);
|
|
104
|
+
if (type) details.extra.unshift(['type', type]);
|
|
105
|
+
const known =
|
|
106
|
+
message ||
|
|
107
|
+
stack ||
|
|
108
|
+
location ||
|
|
109
|
+
details.operator ||
|
|
110
|
+
details.code ||
|
|
111
|
+
details.expected ||
|
|
112
|
+
details.actual;
|
|
113
|
+
if (!known) details.message = fallback;
|
|
114
|
+
const source = show(map.get('at')) || stack || location;
|
|
115
|
+
const key = errorKey({ ...details, type }, source, root);
|
|
116
|
+
const trace = userTrace(parsed.get('stack') || stack || location, root);
|
|
117
|
+
return problem({ ...details, key, trace });
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const quoted = (value) => value.startsWith('"') || value.startsWith('\x27');
|
|
121
|
+
|
|
122
|
+
const scalarText = (value) => {
|
|
123
|
+
if (value.startsWith('"')) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(value.replace(/\r?\n/g, '\\n'));
|
|
126
|
+
} catch {
|
|
127
|
+
return unquote(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return unquote(value);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const blockText = (lines) => {
|
|
134
|
+
let indent = Infinity;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
if (line.trim()) indent = Math.min(indent, line.match(/^\s*/)[0].length);
|
|
137
|
+
}
|
|
138
|
+
return lines
|
|
139
|
+
.map((line) => line.slice(indent))
|
|
140
|
+
.join('\n')
|
|
141
|
+
.trimEnd();
|
|
72
142
|
};
|
|
73
143
|
|
|
74
144
|
const yamlFields = (lines) => {
|
|
@@ -81,38 +151,53 @@ const yamlFields = (lines) => {
|
|
|
81
151
|
continue;
|
|
82
152
|
}
|
|
83
153
|
const key = match[2];
|
|
84
|
-
|
|
154
|
+
let value = match[3].trim().replace(/,$/, '');
|
|
85
155
|
const block = value === '|-' || value === '|' || value === '>';
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
156
|
+
index += 1;
|
|
157
|
+
if (key === 'error' && !value) continue;
|
|
158
|
+
if (quoted(value)) {
|
|
159
|
+
const quote = value[0];
|
|
160
|
+
while (
|
|
161
|
+
index < lines.length &&
|
|
162
|
+
(value.length < 2 || value.at(-1) !== quote)
|
|
163
|
+
) {
|
|
164
|
+
value = `${value}\n${lines[index]}`;
|
|
165
|
+
index += 1;
|
|
166
|
+
}
|
|
167
|
+
fields.push([key, scalarText(value)]);
|
|
89
168
|
continue;
|
|
90
169
|
}
|
|
91
170
|
const rows = [];
|
|
92
|
-
|
|
93
|
-
while (index < lines.length
|
|
94
|
-
|
|
171
|
+
const indent = match[1].length;
|
|
172
|
+
while (index < lines.length) {
|
|
173
|
+
const line = lines[index];
|
|
174
|
+
if (line.trim() && line.match(/^\s*/)[0].length <= indent) break;
|
|
175
|
+
rows.push(lines[index]);
|
|
95
176
|
index += 1;
|
|
96
177
|
}
|
|
97
|
-
|
|
178
|
+
if (block || rows.some((line) => line.trim())) {
|
|
179
|
+
const text = blockText(rows);
|
|
180
|
+
fields.push([key, block ? text : `${value}\n${text}`.trim()]);
|
|
181
|
+
} else {
|
|
182
|
+
fields.push([key, scalarText(value)]);
|
|
183
|
+
}
|
|
98
184
|
}
|
|
99
185
|
return fields;
|
|
100
186
|
};
|
|
101
187
|
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return match[1].trim();
|
|
188
|
+
const tapPoint = (line) => {
|
|
189
|
+
const match = /^\s*(not ok|ok)\b(?:\s+\d+)?(?:\s+-)?(?:\s+(.*))?$/.exec(line);
|
|
190
|
+
if (!match) return null;
|
|
191
|
+
const text = match[2] || '';
|
|
192
|
+
return {
|
|
193
|
+
passed: match[1] === 'ok',
|
|
194
|
+
name: text
|
|
195
|
+
.split(/\s+#/)[0]
|
|
196
|
+
.replace(/\s*\{\s*$/, '')
|
|
197
|
+
.trim(),
|
|
198
|
+
skipped: /#\s*(SKIP|TODO)\b/i.test(text),
|
|
199
|
+
container: /\{\s*$/.test(text),
|
|
200
|
+
};
|
|
116
201
|
};
|
|
117
202
|
|
|
118
203
|
const STAT_FIELD = {
|
|
@@ -137,21 +222,57 @@ const applyTapComment = (stats, line) => {
|
|
|
137
222
|
const isTapLine = (line) => {
|
|
138
223
|
const text = line.trim();
|
|
139
224
|
if (text.startsWith('TAP version')) return true;
|
|
140
|
-
if (
|
|
225
|
+
if (tapPoint(text)) return true;
|
|
141
226
|
if (/^1\.\.\d+/.test(text)) return true;
|
|
142
227
|
if (text.startsWith('Bail out!')) return true;
|
|
143
228
|
return false;
|
|
144
229
|
};
|
|
145
230
|
|
|
231
|
+
const tapBoundary = (line) => {
|
|
232
|
+
if (isTapLine(line) || line.trim() === '}') return true;
|
|
233
|
+
const stats =
|
|
234
|
+
/^\s*#\s+(?:Subtest:|(?:tests|pass|fail|skipped|duration_ms)\s+\d)/;
|
|
235
|
+
return stats.test(line);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const readYaml = (lines, start) => {
|
|
239
|
+
const body = [];
|
|
240
|
+
const indent = lines[start].match(/^\s*/)[0].length;
|
|
241
|
+
let index = start + 1;
|
|
242
|
+
for (; index < lines.length; index++) {
|
|
243
|
+
if (lines[index].trim() === '...') break;
|
|
244
|
+
const lead = lines[index].match(/^\s*/)[0].length;
|
|
245
|
+
if (lead <= indent && tapBoundary(lines[index])) break;
|
|
246
|
+
body.push(lines[index]);
|
|
247
|
+
}
|
|
248
|
+
const end = lines[index] && lines[index].trim() === '...';
|
|
249
|
+
return { fields: yamlFields(body), next: end ? index + 1 : index };
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const readTapFields = (lines, start) => {
|
|
253
|
+
const fields = [];
|
|
254
|
+
const body = [];
|
|
255
|
+
let index = start;
|
|
256
|
+
while (index < lines.length && !tapBoundary(lines[index])) {
|
|
257
|
+
if (lines[index].trim() === '---') {
|
|
258
|
+
const yaml = readYaml(lines, index);
|
|
259
|
+
fields.push(...yaml.fields);
|
|
260
|
+
index = yaml.next;
|
|
261
|
+
} else {
|
|
262
|
+
body.push(lines[index]);
|
|
263
|
+
index += 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!fields.length) fields.push(...exceptionFields(body));
|
|
267
|
+
return { fields, next: index };
|
|
268
|
+
};
|
|
269
|
+
|
|
146
270
|
const parseTap = (text, root) => {
|
|
147
271
|
const lines = `${text ?? ''}`.split(/\r?\n/);
|
|
148
|
-
|
|
149
|
-
for (const line of lines) {
|
|
150
|
-
if (isTapLine(line)) seen = true;
|
|
151
|
-
}
|
|
152
|
-
if (!seen) return null;
|
|
272
|
+
if (!lines.some(isTapLine)) return null;
|
|
153
273
|
const stats = summary();
|
|
154
274
|
const problems = [];
|
|
275
|
+
const counts = { tests: 0, passed: 0, failed: 0, skipped: 0 };
|
|
155
276
|
let index = 0;
|
|
156
277
|
while (index < lines.length) {
|
|
157
278
|
const line = lines[index];
|
|
@@ -160,73 +281,67 @@ const parseTap = (text, root) => {
|
|
|
160
281
|
if (trimmed.startsWith('Bail out!')) {
|
|
161
282
|
problems.push(problem({ name: 'Bail out!', message: trimmed }));
|
|
162
283
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
if (!failed && !passed) {
|
|
284
|
+
const point = tapPoint(line);
|
|
285
|
+
if (!point || point.container) {
|
|
166
286
|
index += 1;
|
|
167
287
|
continue;
|
|
168
288
|
}
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
289
|
+
const body = readTapFields(lines, index + 1);
|
|
290
|
+
index = body.next;
|
|
291
|
+
const failure = new Map(body.fields).get('failureType');
|
|
292
|
+
if (failure === 'subtestsFailed') continue;
|
|
293
|
+
counts.tests += 1;
|
|
294
|
+
if (point.skipped) {
|
|
295
|
+
counts.skipped += 1;
|
|
296
|
+
} else if (point.passed) {
|
|
297
|
+
counts.passed += 1;
|
|
175
298
|
} else {
|
|
176
|
-
|
|
299
|
+
counts.failed += 1;
|
|
300
|
+
problems.push(
|
|
301
|
+
problemFromFields(point.name, body.fields, root, point.name),
|
|
302
|
+
);
|
|
177
303
|
}
|
|
178
|
-
if (failed) problems.push(problemFromFields(name, fields, root));
|
|
179
304
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const countStatus = (stats, status) => {
|
|
184
|
-
if (status === 'passed' || status === 'pass') {
|
|
185
|
-
stats.passed = (stats.passed || 0) + 1;
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (status === 'failed' || status === 'fail' || status === 'timedOut') {
|
|
189
|
-
stats.failed = (stats.failed || 0) + 1;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
if (status === 'skipped' || status === 'pending' || status === 'todo') {
|
|
193
|
-
stats.skipped = (stats.skipped || 0) + 1;
|
|
305
|
+
for (const key of Object.keys(counts)) {
|
|
306
|
+
if (stats[key] === null) stats[key] = counts[key];
|
|
194
307
|
}
|
|
308
|
+
return report('test', { summary: stats, problems });
|
|
195
309
|
};
|
|
196
310
|
|
|
197
311
|
const nodeEventProblem = (event, root) => {
|
|
198
312
|
const data = event.data || {};
|
|
199
313
|
const details = data.details || {};
|
|
200
314
|
const error = details.error || {};
|
|
201
|
-
const fields =
|
|
202
|
-
for (const key of Object.keys(error)) fields.push([key, error[key]]);
|
|
315
|
+
const fields = Object.entries(error);
|
|
203
316
|
if (details.duration_ms !== undefined) {
|
|
204
317
|
fields.push(['duration_ms', details.duration_ms]);
|
|
205
318
|
}
|
|
206
|
-
|
|
207
|
-
return problemFromFields(name, fields, root);
|
|
319
|
+
return problemFromFields(data.name || '', fields, root);
|
|
208
320
|
};
|
|
209
321
|
|
|
210
322
|
const parseNodeEvents = (events, root) => {
|
|
211
|
-
const stats = summary();
|
|
212
323
|
const problems = [];
|
|
213
324
|
let seen = false;
|
|
325
|
+
let passed = 0;
|
|
326
|
+
let failed = 0;
|
|
327
|
+
let skipped = 0;
|
|
214
328
|
for (const event of events) {
|
|
215
329
|
const type = event && event.type;
|
|
216
330
|
if (typeof type !== 'string' || !type.startsWith('test:')) continue;
|
|
217
331
|
seen = true;
|
|
218
|
-
if (type === 'test:pass')
|
|
332
|
+
if (type === 'test:pass') passed += 1;
|
|
333
|
+
if (type === 'test:skip' || type === 'test:todo') skipped += 1;
|
|
219
334
|
if (type === 'test:fail') {
|
|
220
|
-
|
|
335
|
+
failed += 1;
|
|
221
336
|
problems.push(nodeEventProblem(event, root));
|
|
222
337
|
}
|
|
223
|
-
if (type === 'test:skip' || type === 'test:todo') {
|
|
224
|
-
countStatus(stats, 'skipped');
|
|
225
|
-
}
|
|
226
338
|
}
|
|
227
339
|
if (!seen) return null;
|
|
228
|
-
const
|
|
229
|
-
stats.tests =
|
|
340
|
+
const stats = summary();
|
|
341
|
+
stats.tests = passed + failed + skipped;
|
|
342
|
+
stats.passed = passed || null;
|
|
343
|
+
stats.failed = failed || null;
|
|
344
|
+
stats.skipped = skipped || null;
|
|
230
345
|
return report('test', { summary: stats, problems });
|
|
231
346
|
};
|
|
232
347
|
|
|
@@ -384,19 +499,17 @@ const readSpecObject = (lines, start) => {
|
|
|
384
499
|
|
|
385
500
|
const pushSpec = (problems, current, root) => {
|
|
386
501
|
if (!current) return;
|
|
387
|
-
const fields = current.fields;
|
|
388
|
-
|
|
389
|
-
if (current.
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
if (current.actual) item.actual = current.actual;
|
|
394
|
-
problems.push(item);
|
|
502
|
+
const fields = [...current.fields];
|
|
503
|
+
if (current.message) fields.push(['error', current.message.trimEnd()]);
|
|
504
|
+
if (current.trace.length) fields.push(['stack', current.trace.join('\n')]);
|
|
505
|
+
if (current.expected) fields.push(['expected', current.expected]);
|
|
506
|
+
if (current.actual) fields.push(['actual', current.actual]);
|
|
507
|
+
problems.push(problemFromFields(current.name, fields, root));
|
|
395
508
|
};
|
|
396
509
|
|
|
397
510
|
const takeSpecLine = (current, line) => {
|
|
398
511
|
const text = line.trim();
|
|
399
|
-
if (/^(
|
|
512
|
+
if (/^[\w.]*Error(?: \[[^\]]+\])?:/.test(text)) {
|
|
400
513
|
current.message = text;
|
|
401
514
|
return;
|
|
402
515
|
}
|
|
@@ -404,6 +517,9 @@ const takeSpecLine = (current, line) => {
|
|
|
404
517
|
if (expected) current.expected = expected[1];
|
|
405
518
|
const received = /^Received:\s*(.*)$/.exec(text);
|
|
406
519
|
if (received) current.actual = received[1];
|
|
520
|
+
if (current.message && !current.trace.length) {
|
|
521
|
+
current.message = `${current.message}\n${text}`;
|
|
522
|
+
}
|
|
407
523
|
};
|
|
408
524
|
|
|
409
525
|
const takeSpecFrame = (lines, index, current) => {
|
|
@@ -433,12 +549,8 @@ const dropPreviews = (problems) => {
|
|
|
433
549
|
for (const item of problems) {
|
|
434
550
|
if (item.name && hasDetail(item)) named.add(item.name);
|
|
435
551
|
}
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
if (item.name && named.has(item.name) && !hasDetail(item)) continue;
|
|
439
|
-
kept.push(item);
|
|
440
|
-
}
|
|
441
|
-
return kept;
|
|
552
|
+
const isPreview = (item) => named.has(item.name) && !hasDetail(item);
|
|
553
|
+
return problems.filter((item) => !isPreview(item));
|
|
442
554
|
};
|
|
443
555
|
|
|
444
556
|
const parseSpec = (text, root) => {
|
|
@@ -447,9 +559,19 @@ const parseSpec = (text, root) => {
|
|
|
447
559
|
const problems = [];
|
|
448
560
|
let current = null;
|
|
449
561
|
let marked = false;
|
|
562
|
+
let summarized = false;
|
|
450
563
|
let index = 0;
|
|
451
564
|
while (index < lines.length) {
|
|
452
565
|
const line = lines[index];
|
|
566
|
+
if (specHeader(line)) {
|
|
567
|
+
pushSpec(problems, current, root);
|
|
568
|
+
current = null;
|
|
569
|
+
if (!summarized) problems.length = 0;
|
|
570
|
+
summarized = true;
|
|
571
|
+
marked = true;
|
|
572
|
+
index += 1;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
453
575
|
if (specStats(stats, line)) marked = true;
|
|
454
576
|
if (isPassingTest(line)) {
|
|
455
577
|
marked = true;
|