Accept mock.fn() without an original

node:test changes, matching Node 24.22:

- mock.fn() and t.mock.fn() without an original return undefined and
  record the arguments of each call, whatever the callers pass. The
  original and the initial implementation are a function that returns
  undefined, so restore() brings that function back.
- mock.fn<F>() with an explicit function type records F's parameters
  when F's return type is void or includes undefined. A return type
  without undefined, such as mock.fn<() => number>(), throws SC2020.

mock.fn()'s default type, (...args: any[]) => undefined, maps to the
checked-dynamic rest ABI used for program-declared (...args: unknown[])
functions: the calls log records the argument array, and the mock's
wrapper spreads it into the implementation.

A function whose fixed parameters are all unknown or any, or that has
none, now converts to a checked-dynamic rest signature without fixed
parameters, such as (...args: unknown[]) => void: each call passes the
arguments in order, undefined for the ones the caller left out. This is
how () => {} reaches mockImplementation and mockImplementationOnce, and
it applies to any such slot in TypeScript programs, which threw SC1090
before.

The mock-fn-default fixture and corpus program 5816 compare with Node,
plain and with SCRIPTC_SAN=1.
This commit is contained in:
Chris Tate
2026-10-19 16:59:05 -05:00
parent 706f473559
commit 0e02fc28ca
17 changed files with 318 additions and 69 deletions
@@ -1,2 +1,2 @@
- **Test mocks:** `mock.fn`, `mock.method`, `mock.restoreAll` and `mock.reset` compile, on the module's `mock` and on `t.mock`. `mock.method` mocks a function-valued field of a record, such as an object literal or an interface-typed object. Class instances, builtin objects, and object literals whose methods use `this` throw `SC2020`. Calls through the object record it as `this`, including calls of a method read off the object and called on its own, where Node records `undefined`. `object.method === mock` is `false`, because the field holds a wrapper of the mock. `mock.fn()` without an original, `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`.
- **Test mocks:** `mock.fn`, `mock.method`, `mock.restoreAll` and `mock.reset` compile, on the module's `mock` and on `t.mock`. `mock.method` mocks a function-valued field of a record, such as an object literal or an interface-typed object. Class instances, builtin objects, and object literals whose methods use `this` throw `SC2020`. Calls through the object record it as `this`, including calls of a method read off the object and called on its own, where Node records `undefined`. `object.method === mock` is `false`, because the field holds a wrapper of the mock. `mock.fn()` without an original returns `undefined` and records the arguments of each call, as in Node; with a function type whose return type does not include `undefined`, such as `mock.fn<() => number>()`, it throws `SC2020`. `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`.
- **Mock timers:** `mock.timers.enable`, `tick`, `runAll`, `reset`, `setTime` and `[Symbol.dispose]()` compile, on the module's `mock` and on `t.mock`, for the `setTimeout`, `setInterval`, `setImmediate`, `Date`, `scheduler.wait` and `AbortSignal.timeout` APIs. `enable()` takes no options or an object literal whose `apis`, when given, is an array literal. Options and `apis` lists that are not literals, and the `MockTimers` object used as a value, throw `SC2020`. As in Node, the global timer functions, `Date.now()`, `new Date()` and `scheduler.wait` are mocked, and other calls through named and namespace imports of `node:timers` and `node:timers/promises` use real timers. As in Node, once a reset gives `scheduler.wait` back, calling it throws `ERR_INVALID_THIS`. While `scheduler.wait` is mocked, calling it with a `signal` option throws an `SC2020` error. A mock timer's handle converts to a negative number, where Node's mock `Timeout` converts to `NaN`. When two trackers have timers enabled, clearing a timer through the tracker that did not create it leaves the timer queued, where Node removes the entry at the same queue position in the clearing tracker.
@@ -1 +1 @@
- **Null and optional values:** `undefined` and `null` compile as standalone values and union members. Optional record and class fields and optional, default, and rest parameters also compile, including rest parameters typed `unknown[]` or `any[]`, whose functions take any number of arguments. A function value with fixed parameters cannot be stored where such a variadic function type is expected.
- **Null and optional values:** `undefined` and `null` compile as standalone values and union members. Optional record and class fields and optional, default, and rest parameters also compile, including rest parameters typed `unknown[]` or `any[]`, whose functions take any number of arguments. A function value whose fixed parameters are all `unknown` or `any`, or that has none, can be stored where such a variadic function type without fixed parameters is expected: each call passes the arguments in order, `undefined` for the ones the caller left out. Other function values with fixed parameters cannot be stored there.
@@ -66502,6 +66502,7 @@
"source": "compiler-feature:test.mock.fn",
"tests": [
"tests/fixtures/node-test/cases/mock-fn.ts",
"tests/fixtures/node-test/cases/mock-fn-default.ts",
"tests/harness/node-test.test.ts"
]
}
@@ -91073,6 +91073,7 @@
"tests/fixtures/node-test/cases/ctx-plan.ts",
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
"tests/fixtures/node-test/cases/get-test-context.ts",
"tests/fixtures/node-test/cases/mock-fn-default.ts",
"tests/fixtures/node-test/cases/mock-fn.ts",
"tests/fixtures/node-test/cases/mock-method.ts",
"tests/fixtures/node-test/cases/mock-timers-date.ts",
@@ -92484,6 +92485,7 @@
"evidence": "compiler-feature:test.mock.fn",
"tests": [
"tests/fixtures/node-test/cases/mock-fn.ts",
"tests/fixtures/node-test/cases/mock-fn-default.ts",
"tests/harness/node-test.test.ts"
]
},
+1 -1
View File
@@ -362,7 +362,7 @@
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.filePath", "passed", "attempt", "tags", "workerId"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-members.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `SuiteContext`", "symbols": ["context.name", "context.fullName", "context.filePath", "context.diagnostic", "passed", "attempt"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-context.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.diagnostic", "context.name"], "status": "partial", "evidence": ["tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.method", "mock.reset", "mock.restoreAll"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-method.ts", "tests/corpus/5810-mock-method-records.ts", "tests/corpus/5811-mock-tracker-restore.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockTimers`", "symbols": ["timers.enable", "timers.reset", "timers.tick", "timers.runAll", "timers.setTime"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers.ts", "tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/corpus/5813-mock-timers-module.ts", "tests/corpus/5814-mock-timers-date.ts", "tests/harness/node-test.test.ts"] },
{ "chapter": "test", "parent": "Class: `MockTimers`", "signatures": ["`timers[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/harness/node-test.test.ts"] },
@@ -127,15 +127,71 @@ export function cleanFuncAdaptable(
return src.ret.kind === "void" && dst.ret.kind === "dyn";
}
/** Adapt a fixed-parameter function to a checked-dynamic rest signature
* without fixed parameters (`() => {}` into mock.fn()'s `(...args: any[])
* => undefined`): the adapter receives the hidden argument array and
* passes its element i as parameter i, undefined when the caller passed
* fewer arguments, as JS does. Null unless every parameter is
* checked-dynamic, which holds a missing argument as undefined, and the
* result is discarded or converts. */
function funcIntoDynRestAdapter(
lowerer: Lowerer,
fromT: IrType & { kind: "func" },
toT: IrType & { kind: "func" },
loc: SrcLoc,
): string | null {
if (fromT.rest === true || fromT.params.some((p) => p.kind !== "dyn")) return null;
if (toT.ret.kind !== "void" && !lowerer.coercibleValue(fromT.ret, toT.ret)) return null;
const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`;
const existing = lowerer.coercions.retags.get(key);
if (existing) return existing;
const name = `%fn.adapt.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.freshClosureAdapters.add(name);
const params: IrParam[] = [{ localId: "rest.0", name: "args", type: DYN }];
const call: IrExpr = {
kind: "callValue",
callee: { kind: "varRef", localId: "f.0", type: fromT, loc },
receiver: { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc },
args: fromT.params.map((_, i): IrExpr => ({
kind: "dynKeyGet",
value: { kind: "varRef", localId: "rest.0", type: DYN, loc },
key: { kind: "strLit", value: String(i), type: STRING, loc },
type: DYN,
loc,
})),
type: fromT.ret,
loc,
};
let body: IrStmt[];
if (toT.ret.kind === "void") {
body = [
{ kind: "exprStmt", expr: call, loc },
{ kind: "return", value: null, loc },
];
} else {
const result = lowerer.coerceToExpected(call, toT.ret);
if (!typeEquals(result.type, toT.ret))
throw new InternalCompilerError("lowerer bug: probed fn-adapter return stopped coercing");
body = [{ kind: "return", value: result, loc }];
}
lowerer.liftedFns.push(...buildFunctionAdapter(name, fromT, toT, params, [], body, loc));
return name;
}
/** Adapt a function value to a destination signature, forwarding and
* converting its arguments and result. Unsupported parameter/result pairs
* produce checked traps when called; rest signatures remain refused. */
* produce checked traps when called; rest signatures remain refused,
* except a fixed-parameter function into a checked-dynamic rest signature
* without fixed parameters (funcIntoDynRestAdapter). */
export function funcCoerceAdapter(
lowerer: Lowerer,
fromT: IrType & { kind: "func" },
toT: IrType & { kind: "func" },
loc: SrcLoc,
): string | null {
if (toT.rest === true && toT.restAbi === undefined && toT.params.length === 0)
return funcIntoDynRestAdapter(lowerer, fromT, toT, loc);
if (fromT.rest === true || toT.rest === true) return null;
if (fromT.params.length > toT.params.length) return null;
// Piece dispositions beyond coercibleValue, all CHECKER-APPROVED
@@ -154,7 +154,12 @@ function mockWrapperFactory(
const { fn, call, calls, once, onceEntry, result, args } = mockContextFields(lowerer, ctxType)!;
const impl = `${name}.impl`;
const ctx = () => varRef("ctx.0", ctxType, loc);
// A checked-dynamic rest function (mock.fn()'s default type) takes its
// arguments as one trailing dyn array, recorded as the call's arguments
// and spread into the implementation.
const dynRest = fn.rest === true && fn.restAbi === undefined;
const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type }));
if (dynRest) params.push({ localId: "rest.0", name: "args", type: DYN });
const paramRefs = () => params.map((p) => varRef(p.localId, p.type, loc));
const argumentsValue = (): IrExpr =>
fn.rest
@@ -219,13 +224,23 @@ function mockWrapperFactory(
loc,
};
};
const invoke: IrExpr = {
kind: "callValue",
callee: varRef("f.0", fn, loc),
args: paramRefs(),
type: fn.ret,
loc,
};
const invoke: IrExpr = dynRest
? {
kind: "dynCall",
callee: { kind: "dynFrom", value: varRef("f.0", fn, loc), type: DYN, loc },
calleeName: "implementation",
args: paramRefs(),
spreads: [{ arg: 0, what: "args" }],
type: DYN,
loc,
}
: {
kind: "callValue",
callee: varRef("f.0", fn, loc),
args: paramRefs(),
type: fn.ret,
loc,
};
const tryBody: IrStmt[] = voidReturn
? [
{ kind: "exprStmt", expr: invoke, loc },
@@ -629,7 +644,37 @@ function requireUndefinedResultArm(
}
}
/** mock.fn(original, implementation?) and t.mock.fn(...). */
/** `%mock.noop.N`: mock.fn()'s original, Node's `function () {}` — it
* returns undefined. Null when the function type's return type cannot hold
* undefined. */
function mockNoopOriginal(
lowerer: Lowerer,
ctxType: IrType,
fn: FuncType,
loc: SrcLoc,
): IrExpr | null {
const voidReturn = fn.ret.kind === "void" || fn.ret.kind === "undefinedT";
const undefinedValue = voidReturn ? null : lowerer.wrappedUndefined(fn.ret, loc);
if (!voidReturn && !undefinedValue) return null;
const name = liftOnce(lowerer, ctxType, "noop", (name) => {
const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type }));
if (fn.rest === true && fn.restAbi === undefined)
params.push({ localId: "rest.0", name: "args", type: DYN });
lowerer.liftedFns.push({
name,
params,
returnType: fn.ret,
locals: params.map((p) => local(p.localId, p.name, p.type)),
body: [{ kind: "return", value: undefinedValue, loc }],
loc,
});
});
return { kind: "closure", fnName: name, captures: [], type: fn, loc };
}
/** mock.fn(original?, implementation?) and t.mock.fn(...). Without an
* original, the mock's original and implementation are a function that
* returns undefined, as in Node. */
export function lowerMockFn(
lowerer: Lowerer,
call: ts.CallExpression,
@@ -658,11 +703,19 @@ export function lowerMockFn(
const { fn } = fields;
requireUndefinedResultArm(lowerer, fields, call, what);
const args = call.arguments.nodes;
if (args.length === 0 || args.length > 2 || args.some(ts.isSpreadElement)) {
if (args.length > 2 || args.some(ts.isSpreadElement)) {
lowerer.noLowering(
`${what} with ${args.length} arguments`,
call,
"the supported forms are mock.fn(original) and mock.fn(original, implementation); mock.fn() and mock options have no lowering",
"the supported forms are mock.fn(), mock.fn(original) and mock.fn(original, implementation); mock options have no lowering",
);
}
const noop = args.length === 0 ? mockNoopOriginal(lowerer, ctxType!, fn, loc) : null;
if (args.length === 0 && !noop) {
lowerer.noLowering(
`${what}() of a function returning '${lowerer.fmt(fn.ret)}'`,
call,
"mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined",
);
}
const stmts: IrStmt[] = [];
@@ -670,7 +723,7 @@ export function lowerMockFn(
stmts.push({
kind: "varDecl",
localId: originalLocal.id,
init: lowerer.lowerExprExpecting(args[0]!, fn),
init: noop ?? lowerer.lowerExprExpecting(args[0]!, fn),
loc,
});
const implementation =
+13 -6
View File
@@ -12,10 +12,11 @@
* object; empty for mock.fn
*
* A MockFunctionCall<F> is { arguments, error, result, this }: the
* arguments as a tuple of F's parameters, the result as F's return type or
* undefined (a checked-dynamic value for functions returning void or
* undefined, since a record field cannot have a unit type), and error and
* this as checked-dynamic values. */
* arguments as a tuple of F's parameters (a checked-dynamic array for
* mock.fn()'s `(...args: any[]) => undefined`), the result as F's return
* type or undefined (a checked-dynamic value for functions returning void
* or undefined, since a record field cannot have a unit type), and error
* and this as checked-dynamic values. */
import { DYN, F64, NULL_T, UNDEFINED_T, VOID, typeKey, type IrType } from "../ir/ir.js";
import type { TypeMapperCtx } from "./type-mapper.js";
@@ -34,11 +35,17 @@ function orUndefined(t: IrType, reg: MockShapeRegistries): IrType {
}
/** The arguments type of a call record: a tuple of the parameters, or the
* rest array itself for a function whose only parameter is a rest array.
* Null for other rest shapes. A function without parameters records the
* rest array itself for a function whose only parameter is a rest array,
* or a checked-dynamic array for a function returning nothing whose only
* parameter is a checked-dynamic rest (mock.fn()'s default, `(...args:
* any[]) => undefined`). Null for other rest shapes. A function without parameters records the
* empty tuple, which maps to an array of the unit-only union, as
* type-mapper.ts maps `[]`. */
function mockArgumentsType(fn: IrType & { kind: "func" }, reg: MockShapeRegistries): IrType | null {
if (fn.rest && fn.restAbi === undefined) {
const returnsNothing = fn.ret.kind === "void" || fn.ret.kind === "undefinedT";
return fn.params.length === 0 && returnsNothing ? DYN : null;
}
if (fn.rest) {
if (fn.params.length !== 1 || fn.params[0]!.kind !== "array") return null;
return fn.params[0]!;
+21 -1
View File
@@ -4384,10 +4384,13 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null
// params). Declaration files keep their existing paths: library
// callback slots such as String#replace's `(substring, ...args: any[])`
// have intrinsic lowerings that inspect the unmapped type.
// node:test's default mock function type, `(...args: any[]) =>
// undefined` (the type parameter default of mock.fn), takes the same
// ABI: mock.fn() without an original records whatever its callers pass.
if (
rest &&
decl !== undefined &&
!decl.getSourceFile().isDeclarationFile &&
(!decl.getSourceFile().isDeclarationFile || isNodeTestDefaultFunctionParam(decl, ctx)) &&
(pt?.kind === "dyn" || (!pt && isAnyArrayType(checker.getTypeOfSymbol(p), checker)))
) {
dynRest = true;
@@ -6364,6 +6367,23 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean {
}
}
/** A parameter of a function type that is the default of a type parameter
* declared in node:test's standard-library declarations (MockTracker.fn's
* `F extends Function = (...args: any[]) => undefined`). */
function isNodeTestDefaultFunctionParam(decl: ts.Declaration, ctx: TypeMapperCtx): boolean {
const fnType = decl.parent;
return (
ts.isParameter(decl) &&
fnType !== undefined &&
ts.isFunctionTypeNode(fnType) &&
fnType.parent !== undefined &&
ts.isTypeParameterDeclaration(fnType.parent) &&
fnType.parent.defaultType === fnType &&
ctx.isStdlibFile(decl.getSourceFile()) &&
isDeclaredInAmbientModule(decl, "test")
);
}
/** `any[]` (or `readonly any[]`): an array type whose element is `any`. */
export function isAnyArrayType(t: ts.Type, checker: ts.TypeChecker): boolean {
if (!checker.isArrayType(t)) return false;
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5816-fixed-into-unknown-rest.ts"
],
"diags": []
}
@@ -0,0 +1,39 @@
// A function with only `unknown` parameters, or none, flows into a
// `(...args: unknown[]) => R` slot: each call passes the callers'
// arguments in order, undefined for a parameter the caller left out, and
// drops the extra ones, as JS calls do. Sync and async functions, stored
// in variables and arrays and passed as arguments.
type Handler = (...args: unknown[]) => void;
type AsyncHandler = (...args: unknown[]) => Promise<unknown>;
const none: Handler = () => console.log("none");
const one: Handler = (a: unknown) => console.log("one", a);
const two: Handler = (a: unknown, b: unknown) => console.log("two", a, b);
none();
none(1, 2, 3);
one();
one("x");
one("x", "y");
two(1);
two(1, [2], { three: 3 });
const handlers: Handler[] = [none, one, two];
for (const h of handlers) h("all", true);
function dispatch(h: Handler, ...args: unknown[]): void {
h(...args);
}
dispatch(two, "spread", null);
dispatch(one);
const first: AsyncHandler = async (value: unknown) => value;
const pair: AsyncHandler = async (a: unknown, b: unknown) => [a, b];
async function main(): Promise<void> {
console.log(await first(10, 20));
console.log(await first());
console.log(await pair("p"));
console.log(await pair("p", "q", "r"));
}
main().then(() => console.log("done"));
@@ -30,8 +30,3 @@ console.log(typeof widened);
// `instanceof Promise` on a value that is never a promise.
const record = { v: 1 };
console.log(record instanceof Promise);
// A variadic `unknown[]` slot takes closures with the same variadic ABI.
type HandlerFn = (...args: unknown[]) => Promise<unknown>;
const fixed: HandlerFn = async (first: unknown) => first;
fixed(1, 2).then((v) => console.log(v));
+8 -4
View File
@@ -1,8 +1,9 @@
// node:test mocks lower for mock.fn(original[, implementation]),
// node:test mocks lower for mock.fn([original[, implementation]]),
// mock.method(object, methodName[, implementation]) on records, their
// t.mock twins, and the trackers' restoreAll and reset. The rest of the
// mock surface is refused, each with a named diagnostic: mock.fn() without
// an original; mock.method on a class instance or a builtin object, with
// an original for a function type whose return type does not include
// undefined; mock.method on a class instance or a builtin object, with
// an options object, or with an implementation of another type when the
// result is used; and functions whose union return type has no undefined
// arm (the calls log records undefined for calls that throw).
@@ -11,8 +12,11 @@ import { mock, test } from "node:test";
const add = mock.fn((a: number, b: number) => a + b); // lowers
add(1, 2);
const empty = mock.fn(); // no original function
empty();
const empty = mock.fn(); // lowers
empty(1, "two");
const counted = mock.fn<() => number>(); // no original, returns number
counted();
const obj = { value: () => 1 };
mock.method(obj, "value"); // lowers
+72
View File
@@ -0,0 +1,72 @@
// mock.fn() and t.mock.fn() without an original: the mock returns
// undefined and records the arguments of each call, whatever its callers
// pass. mockImplementation and mockImplementationOnce replace its
// behavior, and restore() brings back the function that returns undefined.
// mock.fn<F>() records F's parameters, for a function type whose return
// type is void or includes undefined.
import assert from "node:assert";
import { mock, test } from "node:test";
test("records calls with any arguments", () => {
const fn = mock.fn();
fn(1, "two");
fn();
fn(true, null, [3]);
assert.strictEqual(fn.mock.callCount(), 3);
const [first, second, third] = fn.mock.calls;
assert.deepStrictEqual(first!.arguments, [1, "two"]);
assert.strictEqual(second!.arguments.length, 0);
assert.deepStrictEqual(third!.arguments, [true, null, [3]]);
assert.ok(first!.result === undefined);
assert.ok(first!.error === undefined);
assert.ok(first!.this === undefined);
});
test("changes implementations", () => {
const seen: string[] = [];
const fn = mock.fn();
fn.mock.mockImplementation((...args: unknown[]) => {
seen.push(`impl ${args.length}`);
return undefined;
});
fn.mock.mockImplementationOnce(() => {
seen.push("once");
return undefined;
});
fn("a");
fn("a", "b");
fn.mock.restore();
fn("c");
assert.deepStrictEqual(seen, ["once", "impl 2"]);
assert.strictEqual(fn.mock.callCount(), 3);
fn.mock.resetCalls();
assert.strictEqual(fn.mock.callCount(), 0);
});
test("records a throwing implementation", () => {
const fn = mock.fn();
fn.mock.mockImplementation(() => {
throw new Error("boom");
});
assert.throws(() => fn(7), { message: "boom" });
const call = fn.mock.calls[0]!;
assert.deepStrictEqual(call.arguments, [7]);
assert.ok(call.error instanceof Error);
});
test("t.mock.fn() and typed mock.fn<F>()", (t) => {
const fn = t.mock.fn();
fn("x");
assert.strictEqual(fn.mock.callCount(), 1);
const typed = t.mock.fn<(a: number, b: string) => void>();
typed(1, "x");
typed(2, "y");
assert.strictEqual(typed.mock.callCount(), 2);
assert.strictEqual(typed.mock.calls[1]!.arguments[0], 2);
assert.strictEqual(typed.mock.calls[1]!.arguments[1], "y");
const maybe = t.mock.fn<() => number | undefined>();
assert.ok(maybe() === undefined);
maybe.mock.mockImplementation(() => 4);
assert.ok(maybe() === 4);
assert.ok(maybe.mock.calls[1]!.result === 4);
});
@@ -55,11 +55,4 @@ maybe-promise-fences.ts:32:13 - error SC1090: 'instanceof Promise' on '{ v: numb
31 | const record = { v: 1 };
32 | console.log(record instanceof Promise);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
33 |
maybe-promise-fences.ts:36:26 - error SC1090: '(unknown) => Promise<unknown>' values where '() => Promise<unknown>' is expected is not supported yet
35 | type HandlerFn = (...args: unknown[]) => Promise<unknown>;
36 | const fixed: HandlerFn = async (first: unknown) => first;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | fixed(1, 2).then((v) => console.log(v));
33 |
+30 -30
View File
@@ -1,62 +1,62 @@
mock-fences.ts:14:15 - error SC2020: 'mock.fn of this function type' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:18:17 - error SC2020: 'mock.fn() of a function returning 'number'' is part of the standard library types but has no scriptc lowering yet
13 |
14 | const empty = mock.fn(); // no original function
| ^~~~~~~~~
15 | empty();
17 |
18 | const counted = mock.fn<() => number>(); // no original, returns number
| ^~~~~~~~~~~~~~~~~~~~~~~
19 | counted();
hint: mock functions lower for functions with fixed parameters, or a single rest array parameter, and an implementation of the same type
hint: mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined
mock-fences.ts:27:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:31:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet
26 | }
27 | mock.method(new Counter(), "count"); // class instance
30 | }
31 | mock.method(new Counter(), "count"); // class instance
| ^~~~~~~~~~~~~
28 | mock.method(console, "log"); // builtin object
32 | mock.method(console, "log"); // builtin object
hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal
mock-fences.ts:28:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:32:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet
27 | mock.method(new Counter(), "count"); // class instance
28 | mock.method(console, "log"); // builtin object
31 | mock.method(new Counter(), "count"); // class instance
32 | mock.method(console, "log"); // builtin object
| ^~~~~~~
29 | mock.method(obj, "value", { times: 1 }); // options
33 | mock.method(obj, "value", { times: 1 }); // options
hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal
mock-fences.ts:29:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:33:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet
28 | mock.method(console, "log"); // builtin object
29 | mock.method(obj, "value", { times: 1 }); // options
32 | mock.method(console, "log"); // builtin object
33 | mock.method(obj, "value", { times: 1 }); // options
| ^~~~~~~~~~~~
30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type
34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type
hint: mock options (times, getter and setter) have no lowering
mock-fences.ts:30:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:34:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet
29 | mock.method(obj, "value", { times: 1 }); // options
30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type
33 | mock.method(obj, "value", { times: 1 }); // options
34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type
| ^~~~~~~~~~~
31 | other();
35 | other();
hint: give the implementation the method's parameter and return types, or discard the result
mock-fences.ts:35:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:39:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet
34 | t.mock.method(obj, "value"); // lowers
35 | t.mock.method(obj, "value", { times: 2 }); // options
38 | t.mock.method(obj, "value"); // lowers
39 | t.mock.method(obj, "value", { times: 2 }); // options
| ^~~~~~~~~~~~
36 | });
40 | });
hint: mock options (times, getter and setter) have no lowering
mock-fences.ts:38:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet
mock-fences.ts:42:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet
37 |
38 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined
41 |
42 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 | pick(true);
43 | pick(true);
hint: a union return type needs an undefined arm here (the calls log records undefined for calls that throw)
@@ -0,0 +1 @@
{ "plain": 3028, "san": 5470 }