fix TSamNearest and fully support jump Keymap
All checks were successful
tests / test (push) Successful in 10s
All checks were successful
tests / test (push) Successful in 10s
This commit is contained in:
139
scripts/bdd-with-location.cjs
Normal file
139
scripts/bdd-with-location.cjs
Normal file
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
function reqFromCwd(id) {
|
||||
// Resolve as if we required from the project root (where mocha is installed)
|
||||
return require(require.resolve(id, { paths: [process.cwd()] }));
|
||||
}
|
||||
|
||||
const Mocha = reqFromCwd('mocha');
|
||||
const bdd = reqFromCwd('mocha/lib/interfaces/bdd');
|
||||
|
||||
function escapeRegExp(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
function captureLocationForFile(file, stackStartFn) {
|
||||
const err = new Error();
|
||||
if (Error.captureStackTrace) Error.captureStackTrace(err, stackStartFn);
|
||||
|
||||
const lines = String(err.stack || '').split('\n').slice(1);
|
||||
|
||||
// Prefer exact match for current test file (absolute/normalized path as passed by mocha)
|
||||
const fileRe = new RegExp(`\\(?(${escapeRegExp(file)}):(\\d+):(\\d+)\\)?$`);
|
||||
for (const line of lines) {
|
||||
const m = line.match(fileRe);
|
||||
if (m) return { file: m[1], line: Number(m[2]), column: Number(m[3]) };
|
||||
}
|
||||
|
||||
// Fallback: any frame containing this file path
|
||||
const anyRe = /\(?(.+?):(\d+):(\d+)\)?$/;
|
||||
for (const line of lines) {
|
||||
const m = line.match(anyRe);
|
||||
if (m && m[1] && m[1].includes(file)) {
|
||||
return { file: m[1], line: Number(m[2]), column: Number(m[3]) };
|
||||
}
|
||||
}
|
||||
|
||||
return { file, line: null, column: null };
|
||||
}
|
||||
|
||||
function markTest(test, loc, pendingType) {
|
||||
if (!test) return;
|
||||
test._akLocation = loc;
|
||||
if (pendingType) test._akPendingType = pendingType; // 'todo' | 'skip'
|
||||
}
|
||||
|
||||
function markSuite(suite, loc, pendingType) {
|
||||
if (!suite) return;
|
||||
suite._akLocation = loc;
|
||||
if (pendingType) suite._akPendingType = pendingType; // 'skip'
|
||||
}
|
||||
|
||||
// Register custom interface name
|
||||
Mocha.interfaces['bdd-with-location'] = function (suite) {
|
||||
// First install standard BDD globals
|
||||
bdd(suite);
|
||||
|
||||
// Then wrap globals on pre-require (Mocha passes "file" for each loaded test file)
|
||||
suite.on('pre-require', function (context, file) {
|
||||
if (!context || !file) return;
|
||||
|
||||
function loc(stackStartFn) {
|
||||
return captureLocationForFile(file, stackStartFn);
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Wrap `it`
|
||||
// -------------------------
|
||||
const origIt = context.it;
|
||||
|
||||
function itWrapped(title, fn) {
|
||||
const test = origIt.call(this, title, fn);
|
||||
|
||||
// "todo" detection: it('x') without a function
|
||||
const pendingType = typeof fn !== 'function' ? 'todo' : null;
|
||||
|
||||
markTest(test, loc(itWrapped), pendingType);
|
||||
return test;
|
||||
}
|
||||
|
||||
// Copy properties like `.only`/`.skip` added by Mocha
|
||||
Object.assign(itWrapped, origIt);
|
||||
|
||||
if (typeof origIt.only === 'function') {
|
||||
itWrapped.only = function (title, fn) {
|
||||
const test = origIt.only.call(this, title, fn);
|
||||
const pendingType = typeof fn !== 'function' ? 'todo' : null;
|
||||
markTest(test, loc(itWrapped.only), pendingType);
|
||||
return test;
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof origIt.skip === 'function') {
|
||||
itWrapped.skip = function (title, fn) {
|
||||
const test = origIt.skip.call(this, title, fn);
|
||||
// Explicit skip
|
||||
markTest(test, loc(itWrapped.skip), 'skip');
|
||||
return test;
|
||||
};
|
||||
}
|
||||
|
||||
context.it = itWrapped;
|
||||
if (context.specify) context.specify = itWrapped;
|
||||
|
||||
// -------------------------
|
||||
// Wrap `describe`
|
||||
// -------------------------
|
||||
const origDescribe = context.describe;
|
||||
|
||||
function describeWrapped(title, fn) {
|
||||
const s = origDescribe.call(this, title, fn);
|
||||
markSuite(s, loc(describeWrapped), null);
|
||||
return s;
|
||||
}
|
||||
|
||||
Object.assign(describeWrapped, origDescribe);
|
||||
|
||||
if (typeof origDescribe.only === 'function') {
|
||||
describeWrapped.only = function (title, fn) {
|
||||
const s = origDescribe.only.call(this, title, fn);
|
||||
markSuite(s, loc(describeWrapped.only), null);
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof origDescribe.skip === 'function') {
|
||||
describeWrapped.skip = function (title, fn) {
|
||||
const s = origDescribe.skip.call(this, title, fn);
|
||||
// Suite skip
|
||||
markSuite(s, loc(describeWrapped.skip), 'skip');
|
||||
return s;
|
||||
};
|
||||
}
|
||||
|
||||
context.describe = describeWrapped;
|
||||
if (context.context) context.context = describeWrapped;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Mocha.interfaces['bdd-with-location'];
|
||||
131
scripts/mocha-ndjson-reporter.cjs
Normal file
131
scripts/mocha-ndjson-reporter.cjs
Normal file
@@ -0,0 +1,131 @@
|
||||
'use strict';
|
||||
|
||||
function reqFromCwd(id) {
|
||||
return require(require.resolve(id, { paths: [process.cwd()] }));
|
||||
}
|
||||
|
||||
const Mocha = reqFromCwd('mocha');
|
||||
const {
|
||||
EVENT_RUN_BEGIN,
|
||||
EVENT_RUN_END,
|
||||
EVENT_SUITE_BEGIN,
|
||||
EVENT_TEST_PASS,
|
||||
EVENT_TEST_FAIL,
|
||||
EVENT_TEST_PENDING,
|
||||
} = Mocha.Runner.constants;
|
||||
|
||||
function serializeError(err) {
|
||||
if (!err) return null;
|
||||
return {
|
||||
name: err.name || null,
|
||||
message: err.message || String(err),
|
||||
stack: err.stack || null,
|
||||
};
|
||||
}
|
||||
|
||||
function safeTitlePath(entity) {
|
||||
if (entity && typeof entity.titlePath === 'function') {
|
||||
return entity.titlePath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class NdjsonReporter {
|
||||
constructor(runner /*, options */) {
|
||||
runner
|
||||
.once(EVENT_RUN_BEGIN, () => {
|
||||
this.emit({ event: 'run-begin', total: runner.total });
|
||||
})
|
||||
|
||||
// Optional suite-level event for describe.skip (helps jump to suite definition)
|
||||
.on(EVENT_SUITE_BEGIN, (suite) => {
|
||||
if (suite && suite.pending && suite.title) {
|
||||
this.emit({
|
||||
event: 'suite',
|
||||
status: 'skipped',
|
||||
pendingType: suite._akPendingType || 'skip',
|
||||
title: suite.title,
|
||||
fullTitle: typeof suite.fullTitle === 'function' ? suite.fullTitle() : suite.title,
|
||||
titlePath: safeTitlePath(suite),
|
||||
file: suite.file || (suite._akLocation && suite._akLocation.file) || null,
|
||||
location: suite._akLocation || null,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
.on(EVENT_TEST_PASS, (test) => {
|
||||
this.emit(this.testPayload('passed', test, null));
|
||||
})
|
||||
|
||||
.on(EVENT_TEST_FAIL, (test, err) => {
|
||||
this.emit(this.testPayload('failed', test, serializeError(err)));
|
||||
})
|
||||
|
||||
.on(EVENT_TEST_PENDING, (test) => {
|
||||
// Mocha "pending" includes:
|
||||
// - TODO tests (no function)
|
||||
// - explicit it.skip / describe.skip
|
||||
// - runtime this.skip()
|
||||
// We'll classify:
|
||||
// - pendingType === 'todo' => pending
|
||||
// - otherwise => skipped
|
||||
const inferredPendingType = this.inferPendingType(test);
|
||||
const status = inferredPendingType === 'todo' ? 'pending' : 'skipped';
|
||||
this.emit({
|
||||
...this.testPayload(status, test, null),
|
||||
pendingType: inferredPendingType,
|
||||
});
|
||||
})
|
||||
|
||||
.once(EVENT_RUN_END, () => {
|
||||
this.emit({ event: 'run-end', stats: runner.stats || null });
|
||||
});
|
||||
}
|
||||
|
||||
inferPendingType(test) {
|
||||
// Priority: explicit marker from UI wrappers
|
||||
if (test && test._akPendingType) return test._akPendingType;
|
||||
|
||||
// Heuristic:
|
||||
// - If there's no function (or it's missing), it's likely a TODO-style pending
|
||||
// - If function exists but test ended as pending, it's likely skipped (this.skip())
|
||||
if (!test) return null;
|
||||
|
||||
// In many Mocha versions, TODO tests have test.fn undefined/null
|
||||
if (!test.fn) return 'todo';
|
||||
|
||||
return 'skip';
|
||||
}
|
||||
|
||||
testPayload(status, test, errorObj) {
|
||||
const location = (test && test._akLocation) || null;
|
||||
|
||||
const file =
|
||||
(test && test.file) ||
|
||||
(test && test.parent && test.parent.file) ||
|
||||
(location && location.file) ||
|
||||
null;
|
||||
|
||||
const payload = {
|
||||
event: 'test',
|
||||
status,
|
||||
title: test ? test.title : null,
|
||||
fullTitle: test && typeof test.fullTitle === 'function' ? test.fullTitle() : null,
|
||||
titlePath: safeTitlePath(test),
|
||||
file,
|
||||
location,
|
||||
duration: test && typeof test.duration === 'number' ? test.duration : null,
|
||||
currentRetry: test && typeof test.currentRetry === 'function' ? test.currentRetry() : null,
|
||||
};
|
||||
|
||||
if (status === 'failed') payload.error = errorObj;
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
emit(obj) {
|
||||
process.stdout.write(JSON.stringify(obj) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NdjsonReporter;
|
||||
Reference in New Issue
Block a user