Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,143 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`.rejects fails for promise that resolves 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBe(<dim>)

Expected <red>received</> Promise to reject, instead it resolved to value
<red>4</>"
`;

exports[`.rejects fails non-promise value "a" 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
string: <red>\\"a\\"</>"
`;

exports[`.rejects fails non-promise value [1] 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
array: <red>[1]</>"
`;

exports[`.rejects fails non-promise value [Function anonymous] 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
function: <red>[Function anonymous]</>"
`;

exports[`.rejects fails non-promise value {"a": 1} 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
object: <red>{\\"a\\": 1}</>"
`;

exports[`.rejects fails non-promise value 4 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
number: <red>4</>"
`;

exports[`.rejects fails non-promise value null 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received: <red>null</>"
`;

exports[`.rejects fails non-promise value true 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
boolean: <red>true</>"
`;

exports[`.rejects fails non-promise value undefined 1`] = `
"<dim>expect(<red>received</><dim>).rejects.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received: <red>undefined</>"
`;

exports[`.resolves fails for promise that rejects 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBe(<dim>)

Expected <red>received</> Promise to resolve, instead it rejected to value
<red>undefined</>"
`;

exports[`.resolves fails non-promise value "a" 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
string: <red>\\"a\\"</>"
`;

exports[`.resolves fails non-promise value [1] 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
array: <red>[1]</>"
`;

exports[`.resolves fails non-promise value [Function anonymous] 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
function: <red>[Function anonymous]</>"
`;

exports[`.resolves fails non-promise value {"a": 1} 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
object: <red>{\\"a\\": 1}</>"
`;

exports[`.resolves fails non-promise value 4 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
number: <red>4</>"
`;

exports[`.resolves fails non-promise value null 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received: <red>null</>"
`;

exports[`.resolves fails non-promise value true 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received:
boolean: <red>true</>"
`;

exports[`.resolves fails non-promise value undefined 1`] = `
"<dim>expect(<red>received</><dim>).resolves.toBeDefined(<dim>)

<red>received</> value must be a Promise.
Received: <red>undefined</>"
`;

exports[`.toBe() does not crash on circular references 1`] = `
"<dim>expect(<red>received</><dim>).toBe(<green>expected</><dim>)

Expand Down
89 changes: 89 additions & 0 deletions packages/jest-matchers/src/__tests__/matchers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,95 @@
const jestExpect = require('../');
const {stringify} = require('jest-matcher-utils');

describe('.rejects', () => {
it('should reject', async () => {
await jestExpect(Promise.reject(4)).rejects.toBe(4);
await jestExpect(Promise.reject(4)).rejects.not.toBe(5);
await jestExpect(Promise.reject(4.2)).rejects.toBeCloseTo(4.2, 5);
await jestExpect(Promise.reject((3))).rejects.not.toBeCloseTo(4.2, 5);
await jestExpect(Promise.reject({a: 1, b: 2})).rejects.toMatchObject({a: 1});
await jestExpect(Promise.reject({a: 1, b: 2})).rejects.not.toMatchObject({c: 1});
await jestExpect(Promise.reject(() => {throw new Error();})).rejects.toThrow();
});

[
4,
[1],
{a: 1},
'a',
true,
null,
undefined,
() => {},
].forEach(value => {
it(`fails non-promise value ${stringify(value)}`, async () => {
let error;
try {
await jestExpect(value).rejects.toBeDefined();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

it('fails for promise that resolves', async () => {
let error;
try {
await jestExpect(Promise.resolve(4)).rejects.toBe(4);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

describe('.resolves', () => {
it('should resolve', async () => {
await jestExpect(Promise.resolve(4)).resolves.toBe(4);
await jestExpect(Promise.resolve(4)).resolves.not.toBe(5);
await jestExpect(Promise.resolve(4.2)).resolves.toBeCloseTo(4.2, 5);
await jestExpect(Promise.resolve((3))).resolves.not.toBeCloseTo(4.2, 5);
await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.toMatchObject({a: 1});
await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.not.toMatchObject({c: 1});
await jestExpect(Promise.resolve(() => {throw new Error();})).resolves.toThrow();
});

[
4,
[1],
{a: 1},
'a',
true,
null,
undefined,
() => {},
].forEach(value => {
it(`fails non-promise value ${stringify(value)}`, async () => {
let error;
try {
await jestExpect(value).resolves.toBeDefined();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

it('fails for promise that rejects', async () => {
let error;
try {
await jestExpect(Promise.reject(4)).resolves.toBe(4);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});
describe('.toBe()', () => {
it('does not throw', () => {
jestExpect('a').not.toBe('b');
Expand Down
83 changes: 82 additions & 1 deletion packages/jest-matchers/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
MatchersObject,
RawMatcherFn,
ThrowingMatcherFn,
PromiseMatcherFn,
} from 'types/Matchers';

const matchers = require('./matchers');
Expand All @@ -41,6 +42,12 @@ class JestAssertionError extends Error {
matcherResult: any;
}

const isPromise = obj => {
return !!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function';
};

if (!global[GLOBAL_STATE]) {
Object.defineProperty(global, GLOBAL_STATE, {
value: {
Expand All @@ -56,14 +63,29 @@ if (!global[GLOBAL_STATE]) {

const expect: Expect = (actual: any): ExpectationObject => {
const allMatchers = global[GLOBAL_STATE].matchers;
const expectation = {not: {}};
const expectation = {
not: {},
rejects: {not: {}},
resolves: {not: {}},
};

Object.keys(allMatchers).forEach(name => {
expectation[name] = makeThrowingMatcher(allMatchers[name], false, actual);
expectation.not[name] = makeThrowingMatcher(
allMatchers[name],
true,
actual,
);

expectation.resolves[name] =
makeResolveMatcher(name, allMatchers[name], false, actual);
expectation.resolves.not[name] =
makeResolveMatcher(name, allMatchers[name], true, actual);

expectation.rejects[name] =
makeRejectMatcher(name, allMatchers[name], false, actual);
expectation.rejects.not[name] =
makeRejectMatcher(name, allMatchers[name], true, actual);
});

return expectation;
Expand All @@ -84,6 +106,65 @@ const getMessage = message => {
return message;
};

const makeResolveMatcher = (
matcherName: string,
matcher: RawMatcherFn,
isNot: boolean,
actual: Promise<any>
): PromiseMatcherFn => async (...args) => {
const matcherStatement = `.resolves.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') + '\n\n' +
`${utils.RECEIVED_COLOR('received')} value must be a Promise.\n` +
utils.printWithType('Received', actual, utils.printReceived),
);
}

let result;
try {
result = await actual;
} catch (e) {
throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') + '\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to resolve, ` +
'instead it rejected to value\n' +
` ${utils.printReceived(result)}`
);
}
return makeThrowingMatcher(matcher, isNot, result).apply(null, args);
};

const makeRejectMatcher = (
matcherName: string,
matcher: RawMatcherFn,
isNot: boolean,
actual: Promise<any>
): PromiseMatcherFn => async (...args) => {
const matcherStatement = `.rejects.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') + '\n\n' +
`${utils.RECEIVED_COLOR('received')} value must be a Promise.\n` +
utils.printWithType('Received', actual, utils.printReceived),
);
}

let result;
try {
result = await actual;
} catch (e) {
return makeThrowingMatcher(matcher, isNot, e).apply(null, args);
}

throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') + '\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to reject, ` +
'instead it resolved to value\n' +
` ${utils.printReceived(result)}`
);
};

const makeThrowingMatcher = (
matcher: RawMatcherFn,
isNot: boolean,
Expand Down
9 changes: 9 additions & 0 deletions types/Matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type RawMatcherFn = (
) => ExpectationResult;

export type ThrowingMatcherFn = (actual: any) => void;
export type PromiseMatcherFn = (actual: any) => Promise<void>;
export type MatcherContext = {isNot: boolean};
export type MatcherState = {
assertionCalls?: number,
Expand All @@ -33,6 +34,14 @@ export type MatcherState = {
export type MatchersObject = {[id:string]: RawMatcherFn};
export type Expect = (expected: any) => ExpectationObject;
export type ExpectationObject = {
resolves: {
[id: string]: PromiseMatcherFn,
not: {[id: string]: PromiseMatcherFn},
},
rejects: {
[id: string]: PromiseMatcherFn,
not: {[id: string]: PromiseMatcherFn},
},
[id: string]: ThrowingMatcherFn,
not: {[id: string]: ThrowingMatcherFn},
};