Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement toThrow(message) with a specific error message string #47700

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 99 additions & 7 deletions jest/integration/runtime/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,64 @@ global.it.skip =
globalModifiers.pop();
};

global.jest = {
fn: createMockFunction,
};

const MOCK_FN_TAG = Symbol('mock function');

function createMockFunction<TArgs: $ReadOnlyArray<mixed>, TReturn>(
initialImplementation?: (...TArgs) => TReturn,
): JestMockFn<TArgs, TReturn> {
let implementation: ?(...TArgs) => TReturn = initialImplementation;

const mock: JestMockFn<TArgs, TReturn>['mock'] = {
calls: [],
// $FlowExpectedError[incompatible-type]
lastCall: undefined,
instances: [],
contexts: [],
results: [],
};

const mockFunction = function (this: mixed, ...args: TArgs): TReturn {
let result: JestMockFn<TArgs, TReturn>['mock']['results'][number] = {
isThrow: false,
// $FlowExpectedError[incompatible-type]
value: undefined,
};

if (implementation != null) {
try {
result.value = implementation.apply(this, args);
} catch (error) {
result.isThrow = true;
result.value = error;
}
}

mock.calls.push(args);
mock.lastCall = args;
// $FlowExpectedError[incompatible-call]
mock.instances.push(new.target ? this : undefined);
mock.contexts.push(this);
mock.results.push(result);

if (result.isThrow) {
throw result.value;
}

return result.value;
};

mockFunction.mock = mock;
// $FlowExpectedError[invalid-computed-prop]
mockFunction[MOCK_FN_TAG] = true;

// $FlowExpectedError[prop-missing]
return mockFunction;
}

// flowlint unsafe-getters-setters:off

class Expect {
Expand Down Expand Up @@ -152,26 +210,48 @@ class Expect {
Math.abs(expected - Number(this.#received)) < Math.pow(10, -precision);
if (!this.#isExpectedResult(pass)) {
throw new Error(
`expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to be close to ${expected}`,
);
}
}

toThrow(error: mixed): void {
if (error != null) {
throw new Error('toThrow() implementation does not accept arguments.');
toThrow(expected?: string): void {
if (expected != null && typeof expected !== 'string') {
throw new Error(
'toThrow() implementation only accepts strings as arguments.',
);
}

let pass = false;
try {
// $FlowExpectedError[not-a-function]
this.#received();
} catch {
pass = true;
} catch (error) {
pass = expected != null ? error.message === expected : true;
}
if (!this.#isExpectedResult(pass)) {
throw new Error(
`expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to throw`,
);
}
}

toHaveBeenCalled(): void {
const mock = this.#requireMock();
const pass = mock.calls.length > 0;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called, but it was${this.#isNot ? '' : "n't"}`,
);
}
}

toHaveBeenCalledTimes(times: number): void {
const mock = this.#requireMock();
const pass = mock.calls.length === times;
if (!this.#isExpectedResult(pass)) {
throw new Error(
`Expected ${String(this.#received)}${this.#maybeNotLabel()} to have been called ${times} times, but it was called ${mock.calls.length} times`,
);
}
}
Expand All @@ -183,6 +263,18 @@ class Expect {
#maybeNotLabel(): string {
return this.#isNot ? ' not' : '';
}

#requireMock(): JestMockFn<$ReadOnlyArray<mixed>, mixed>['mock'] {
// $FlowExpectedError[incompatible-use]
if (!this.#received?.[MOCK_FN_TAG]) {
throw new Error(
`Expected ${String(this.#received)} to be a mock function, but it wasn't`,
);
}

// $FlowExpectedError[incompatible-use]
return this.#received.mock;
}
}

global.expect = (received: mixed) => new Expect(received);
Expand Down
Loading