592 lines
18 KiB
TypeScript
592 lines
18 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import { test, expect } from './fixtures';
|
|
import { expect as expectUnderTest } from '../../packages/playwright/bundles/expect/src/expectBundleImpl';
|
|
|
|
const expectUnderTestAsAny = expectUnderTest as any;
|
|
|
|
// Custom Error class because node versions have different stack trace strings.
|
|
class CustomError extends Error {
|
|
constructor(message?: string) {
|
|
super(message);
|
|
this.name = 'Error';
|
|
this.stack =
|
|
'Error\n' +
|
|
' at expectUnderTest' +
|
|
' (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)';
|
|
}
|
|
}
|
|
|
|
for (const toThrow of ['toThrowError', 'toThrow'] as const) {
|
|
test.describe(toThrow, () => {
|
|
class Err extends CustomError {}
|
|
class Err2 extends CustomError {}
|
|
|
|
test('to throw or not to throw', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow]();
|
|
expectUnderTest(() => {}).not[toThrow]();
|
|
});
|
|
|
|
test.describe('substring', () => {
|
|
test('passes', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow]('apple');
|
|
expectUnderTest(() => {
|
|
throw new CustomError('banana');
|
|
}).not[toThrow]('apple');
|
|
expectUnderTest(() => {}).not[toThrow]('apple');
|
|
});
|
|
|
|
test('did not throw at all', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {})[toThrow]('apple'),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message did not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow]('banana');
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message did not match (non-error falsey)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw '';
|
|
})[toThrow]('Server Error');
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('properly escapes strings when matching against errors', () => {
|
|
expectUnderTest(() => {
|
|
throw new TypeError('"this"? throws.');
|
|
})[toThrow]('"this"? throws.');
|
|
});
|
|
|
|
test('threw, but message should not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('Invalid array length');
|
|
}).not[toThrow]('array');
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message should not match (non-error truthy)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw 'Internal Server Error';
|
|
}).not[toThrow]('Server Error');
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
test.describe('regexp', () => {
|
|
test('passes', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](/apple/);
|
|
expectUnderTest(() => {
|
|
throw new CustomError('banana');
|
|
}).not[toThrow](/apple/);
|
|
expectUnderTest(() => {}).not[toThrow](/apple/);
|
|
});
|
|
|
|
test('did not throw at all', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {})[toThrow](/apple/),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message did not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](/banana/);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message did not match (non-error falsey)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw 0;
|
|
})[toThrow](/^[123456789]\d*/);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message should not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('Invalid array length');
|
|
}).not[toThrow](/ array /);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but message should not match (non-error truthy)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw 404;
|
|
}).not[toThrow](/^[123456789]\d*/);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
test.describe('error class', () => {
|
|
class SubErr extends Err {
|
|
constructor(message?: string) {
|
|
super(message);
|
|
// In a carefully written error subclass,
|
|
// name property is equal to constructor name.
|
|
this.name = this.constructor.name;
|
|
}
|
|
}
|
|
|
|
class SubSubErr extends SubErr {
|
|
constructor(message?: string) {
|
|
super(message);
|
|
// In a carefully written error subclass,
|
|
// name property is equal to constructor name.
|
|
this.name = this.constructor.name;
|
|
}
|
|
}
|
|
|
|
test('passes', () => {
|
|
expectUnderTest(() => {
|
|
throw new Err();
|
|
})[toThrow](Err);
|
|
expectUnderTest(() => {
|
|
throw new Err();
|
|
})[toThrow](CustomError);
|
|
expectUnderTest(() => {
|
|
throw new Err();
|
|
}).not[toThrow](Err2);
|
|
expectUnderTest(() => {}).not[toThrow](Err);
|
|
});
|
|
|
|
test('did not throw at all', () => {
|
|
expect(() =>
|
|
expect(() => {})[toThrow](Err),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class did not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
})[toThrow](Err2);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class did not match (non-error falsey)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw undefined;
|
|
})[toThrow](Err2);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class should not match (error)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
}).not[toThrow](Err);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class should not match (error subclass)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new SubErr('apple');
|
|
}).not[toThrow](Err);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class should not match (error subsubclass)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
throw new SubSubErr('apple');
|
|
}).not[toThrow](Err);
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
test.describe('error-message', () => {
|
|
// Received message in report if object has message property.
|
|
class ErrorMessage {
|
|
// not extending Error!
|
|
constructor(public message: string) {}
|
|
}
|
|
const expected = new ErrorMessage('apple');
|
|
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new ErrorMessage('apple');
|
|
})[toThrow](expected);
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expectUnderTest(() => {
|
|
throw new ErrorMessage('banana');
|
|
}).not[toThrow](expected);
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new ErrorMessage('banana');
|
|
})[toThrow](expected),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
const message = 'Invalid array length';
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new ErrorMessage(message);
|
|
}).not[toThrow]({ message }),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('multiline diff highlight incorrect expected space', () => {
|
|
// jest/issues/2673
|
|
const a =
|
|
"There is no route defined for key Settings. \nMust be one of: 'Home'";
|
|
const b =
|
|
"There is no route defined for key Settings.\nMust be one of: 'Home'";
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new ErrorMessage(b);
|
|
})[toThrow]({ message: a }),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('error message and cause', () => {
|
|
const errorA = new Error('A');
|
|
const errorB = new Error('B', { cause: errorA });
|
|
const expected = new Error('good', { cause: errorB });
|
|
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new Error('good', { cause: errorB });
|
|
})[toThrow](expected);
|
|
});
|
|
|
|
test('isNot true, incorrect message', () => {
|
|
expectUnderTest(() => {
|
|
throw new Error('bad', { cause: errorB });
|
|
}).not[toThrow](expected);
|
|
});
|
|
|
|
test('isNot true, incorrect cause', () => {
|
|
expectUnderTest(() => {
|
|
throw new Error('good', { cause: errorA });
|
|
}).not[toThrow](expected);
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false, incorrect message', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new Error('bad', { cause: errorB });
|
|
})[toThrow](expected),
|
|
).toThrow(
|
|
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
|
|
);
|
|
});
|
|
|
|
test('isNot true, incorrect cause', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new Error('good', { cause: errorA });
|
|
})[toThrow](expected),
|
|
).toThrow(
|
|
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('asymmetric', () => {
|
|
test.describe('any-Class', () => {
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
})[toThrow](expect.any(Err));
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
}).not[toThrow](expect.any(Err2));
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
})[toThrow](expect.any(Err2)),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new Err('apple');
|
|
}).not[toThrow](expect.any(Err)),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('anything', () => {
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](expect.anything());
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expectUnderTest(() => {}).not[toThrow](expect.anything());
|
|
expectUnderTest(() => {
|
|
|
|
throw null;
|
|
}).not[toThrow](expect.anything());
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
|
|
throw null;
|
|
})[toThrow](expect.anything()),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
}).not[toThrow](expect.anything()),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('no-symbol', () => {
|
|
// Test serialization of asymmetric matcher which has no property:
|
|
// this.$$typeof = Symbol.for('jest.asymmetricMatcher')
|
|
const matchError = {
|
|
asymmetricMatch(received: Error | null | undefined) {
|
|
return (
|
|
received !== null &&
|
|
received !== undefined &&
|
|
received.name === 'Error'
|
|
);
|
|
},
|
|
};
|
|
const matchNotError = {
|
|
asymmetricMatch(received: Error | null | undefined) {
|
|
return (
|
|
received !== null &&
|
|
received !== undefined &&
|
|
received.name !== 'Error'
|
|
);
|
|
},
|
|
};
|
|
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](matchError);
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
}).not[toThrow](matchNotError);
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](matchNotError),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
}).not[toThrow](matchError),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('objectContaining', () => {
|
|
const matchError = expect.objectContaining({
|
|
name: 'Error',
|
|
});
|
|
const matchNotError = expect.objectContaining({
|
|
name: 'NotError',
|
|
});
|
|
|
|
test.describe('pass', () => {
|
|
test('isNot false', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](matchError);
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
}).not[toThrow](matchNotError);
|
|
});
|
|
});
|
|
|
|
test.describe('fail', () => {
|
|
test('isNot false', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
})[toThrow](matchNotError),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('isNot true', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {
|
|
throw new CustomError('apple');
|
|
}).not[toThrow](matchError),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('promise/async throws if Error-like object is returned', () => {
|
|
const asyncFn = async (shouldThrow?: boolean, resolve?: boolean) => {
|
|
let err;
|
|
if (shouldThrow)
|
|
err = new Err('async apple');
|
|
|
|
if (resolve)
|
|
return Promise.resolve(err || 'apple');
|
|
else
|
|
return Promise.reject(err || 'apple');
|
|
};
|
|
|
|
test('passes', async () => {
|
|
await expectUnderTest(Promise.reject(new Error())).rejects[toThrow]();
|
|
|
|
await expectUnderTest(asyncFn(true)).rejects[toThrow]();
|
|
await expectUnderTest(asyncFn(true)).rejects[toThrow](Err);
|
|
await expectUnderTest(asyncFn(true)).rejects[toThrow](Error);
|
|
await expectUnderTest(asyncFn(true)).rejects[toThrow]('apple');
|
|
await expectUnderTest(asyncFn(true)).rejects[toThrow](/app/);
|
|
|
|
await expectUnderTest(asyncFn(true)).rejects.not[toThrow](Err2);
|
|
await expectUnderTest(asyncFn(true)).rejects.not[toThrow]('banana');
|
|
await expectUnderTest(asyncFn(true)).rejects.not[toThrow](/banana/);
|
|
|
|
await expectUnderTest(asyncFn(true, true)).resolves[toThrow]();
|
|
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow]();
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow](Error);
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow]('apple');
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow](/apple/);
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow]('banana');
|
|
await expectUnderTest(asyncFn(false, true)).resolves.not[toThrow](/banana/);
|
|
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow]();
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow](Error);
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow]('apple');
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow](/apple/);
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow]('banana');
|
|
await expectUnderTest(asyncFn()).rejects.not[toThrow](/banana/);
|
|
|
|
// Works with nested functions inside promises
|
|
await expectUnderTest(
|
|
Promise.reject(() => {
|
|
throw new Error();
|
|
}),
|
|
).rejects[toThrow]();
|
|
await expectUnderTest(Promise.reject(() => {})).rejects.not[toThrow]();
|
|
});
|
|
|
|
test('did not throw at all', async () => {
|
|
await expectUnderTestAsAny(
|
|
expectUnderTest(asyncFn()).rejects[toThrow](),
|
|
).rejects.toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but class did not match', async () => {
|
|
await expectUnderTestAsAny(
|
|
expectUnderTest(asyncFn(true)).rejects[toThrow](Err2),
|
|
).rejects.toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('threw, but should not have', async () => {
|
|
await expectUnderTestAsAny(
|
|
expectUnderTest(asyncFn(true)).rejects.not[toThrow](),
|
|
).rejects.toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
test.describe('expected is undefined', () => {
|
|
test('threw, but should not have (non-error falsey)', () => {
|
|
expect(() => {
|
|
expectUnderTest(() => {
|
|
|
|
throw null;
|
|
}).not[toThrow]();
|
|
}).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
|
|
test('invalid arguments', () => {
|
|
expect(() =>
|
|
expectUnderTest(() => {}).not[toThrow](111),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
test('invalid actual', () => {
|
|
expect(() =>
|
|
expectUnderTest('a string')[toThrow](),
|
|
).toThrowErrorMatchingSnapshot();
|
|
});
|
|
});
|
|
}
|