diff --git a/README.md b/README.md index 44b8a5e9..e282b6e2 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ If you've come here to help contribute - Thanks! Take a look at the [contributin - [.toBeBoolean()](#tobeboolean) - [.toBeTrue()](#tobetrue) - [.toBeFalse()](#tobefalse) + - [Console](#console) + - [.toLog()](#tolog) - [Date](#date) - [.toBeDate()](#tobedate) - [.toBeValidDate()](#tobevaliddate) @@ -356,6 +358,21 @@ test('returns false', () => { }); ``` +### Console + +#### .toLog() + +Use `.toLog` when checking if a callback function outputs a message to the console. The specified console method is mocked during execution so nothing will be printed to the console. + +```js +expect(() => { + console.log('a message'); +}).toLog('a message'); + +expect(someFunction).toLog('some deprecation warning', 'warn') +expect(someOtherFunction).not.toLog('an error message', 'error') +``` + ### ~~Date~~ Proposal in #117 (*under development*) diff --git a/src/matchers/toLog/__snapshots__/index.test.js.snap b/src/matchers/toLog/__snapshots__/index.test.js.snap new file mode 100644 index 00000000..8e5615f5 --- /dev/null +++ b/src/matchers/toLog/__snapshots__/index.test.js.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.not.toLog fails when the callback function outputs the expected message 1`] = ` +"expect(received).not.toLog(expected) + +Expected console.log not to have been called with: + \\"console-log\\" +But it was called with: + [\\"console-log\\"]" +`; + +exports[`.toLog fails when the callback function does not output any message 1`] = ` +"expect(received).toLog(expected) + +Expected console.log to have been called with: + \\"something\\" +But it was not called" +`; + +exports[`.toLog fails when the callback function does not output the expected message 1`] = ` +"expect(received).toLog(expected) + +Expected console.log to have been called with: + \\"something\\" +But it was called with: + [\\"console-log\\"]" +`; + +exports[`.toLog fails when the callback function does not output the expected message 2`] = ` +"expect(received).toLog(expected) + +Expected console.log to have been called with: + \\"something\\" +But it was called with: + [\\"a\\", \\"b\\", \\"c\\", \\"console-log\\"]" +`; diff --git a/src/matchers/toLog/index.js b/src/matchers/toLog/index.js new file mode 100644 index 00000000..e4a2981f --- /dev/null +++ b/src/matchers/toLog/index.js @@ -0,0 +1,30 @@ +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; + +import predicate from './predicate'; + +const calledWith = actual => + actual ? 'But it was called with:\n' + ` ${printReceived(actual)}` : 'But it was not called'; + +const passMessage = (actual, expected) => () => + matcherHint('.not.toLog') + + '\n\n' + + 'Expected console.log not to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +const failMessage = (actual, expected) => () => + matcherHint('.toLog') + + '\n\n' + + 'Expected console.log to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +export default { + toLog: (fn, expected) => { + const { actual, pass } = predicate(fn, expected); + if (pass) { + return { pass: true, message: passMessage(actual, expected) }; + } + return { pass: false, message: failMessage(actual, expected) }; + } +}; diff --git a/src/matchers/toLog/index.test.js b/src/matchers/toLog/index.test.js new file mode 100644 index 00000000..b35bf388 --- /dev/null +++ b/src/matchers/toLog/index.test.js @@ -0,0 +1,45 @@ +import matcher from './'; + +expect.extend(matcher); + +const log = () => console.log('console-log'); // eslint-disable-line +const noop = () => {}; + +describe('.toLog', () => { + test('passes when the callback function outputs the expected message to the console', () => { + expect(log).toLog('console-log'); + expect(() => console.log(1)).toLog(1); // eslint-disable-line + expect(() => console.log([1, 2])).toLog([1, 2]); // eslint-disable-line + expect(() => console.log({ a: 1, b: 2 })).toLog({ a: 1, b: 2 }); // eslint-disable-line + }); + test('fails when the callback function does not output any message', () => { + expect(() => expect(noop).toLog('something')).toThrowErrorMatchingSnapshot(); + }); + test('fails when the callback function does not output the expected message', () => { + expect(() => expect(log).toLog('something')).toThrowErrorMatchingSnapshot(); + expect(() => + expect(() => { + console.log('a', 'b', 'c'); // eslint-disable-line + log(); + }).toLog('something') + ).toThrowErrorMatchingSnapshot(); + }); + test('passes when the callback function outputs the expected message among others', () => { + expect(() => { + console.log('something'); // eslint-disable-line + log(); + }).toLog('console-log'); + }); +}); + +describe('.not.toLog', () => { + test('passes when the callback function does not output anything', () => { + expect(noop).not.toLog('something'); + }); + test('passes when the callback function does not output the expected message', () => { + expect(log).not.toLog('something'); + }); + test('fails when the callback function outputs the expected message', () => { + expect(() => expect(log).not.toLog('console-log')).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/matchers/toLog/predicate.js b/src/matchers/toLog/predicate.js new file mode 100644 index 00000000..23540907 --- /dev/null +++ b/src/matchers/toLog/predicate.js @@ -0,0 +1,17 @@ +import { equals } from 'expect/build/jasmine_utils'; + +export default (fn, expected) => { + const log = console.log; // eslint-disable-line no-console + const actual = []; + console.log = (...args) => actual.push(...args); // eslint-disable-line no-console + try { + fn(); + } finally { + console.log = log; // eslint-disable-line no-console + } + if (actual.length < 1) { + return { pass: false }; + } + const pass = actual.some(message => equals(message, expected)); + return { actual, pass }; +}; diff --git a/src/matchers/toLog/predicate.test.js b/src/matchers/toLog/predicate.test.js new file mode 100644 index 00000000..1d4d5906 --- /dev/null +++ b/src/matchers/toLog/predicate.test.js @@ -0,0 +1,18 @@ +import predicate from './predicate'; + +const fn = () => console.log('console-log'); // eslint-disable-line + +describe('Predicate > .toLog', () => { + test('returns an object with pass status and the actual output', () => { + const expected = 'console-log'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(true); + expect(actual).toEqual(['console-log']); + }); + it('returns an object with fail status and the actual output', () => { + const expected = 'something'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(false); + expect(actual).toEqual(['console-log']); + }); +}); diff --git a/src/matchers/toLogError/__snapshots__/index.test.js.snap b/src/matchers/toLogError/__snapshots__/index.test.js.snap new file mode 100644 index 00000000..29334123 --- /dev/null +++ b/src/matchers/toLogError/__snapshots__/index.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.not.toLogError fails when the callback function outputs the expected message 1`] = ` +"expect(received).not.toLogError(expected) + +Expected console.error not to have been called with: + \\"console-error\\" +But it was called with: + [\\"console-error\\"]" +`; + +exports[`.toLogError fails when the callback function does not output any message 1`] = ` +"expect(received).toLogError(expected) + +Expected console.error to have been called with: + \\"something\\" +But it was not called" +`; + +exports[`.toLogError fails when the callback function does not output the expected message 1`] = ` +"expect(received).toLogError(expected) + +Expected console.error to have been called with: + \\"something\\" +But it was called with: + [\\"console-error\\"]" +`; diff --git a/src/matchers/toLogError/index.js b/src/matchers/toLogError/index.js new file mode 100644 index 00000000..0d9287be --- /dev/null +++ b/src/matchers/toLogError/index.js @@ -0,0 +1,30 @@ +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; + +import predicate from './predicate'; + +const calledWith = actual => + actual ? 'But it was called with:\n' + ` ${printReceived(actual)}` : 'But it was not called'; + +const passMessage = (actual, expected) => () => + matcherHint('.not.toLogError') + + '\n\n' + + 'Expected console.error not to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +const failMessage = (actual, expected) => () => + matcherHint('.toLogError') + + '\n\n' + + 'Expected console.error to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +export default { + toLogError: (fn, expected) => { + const { actual, pass } = predicate(fn, expected); + if (pass) { + return { pass: true, message: passMessage(actual, expected) }; + } + return { pass: false, message: failMessage(actual, expected) }; + } +}; diff --git a/src/matchers/toLogError/index.test.js b/src/matchers/toLogError/index.test.js new file mode 100644 index 00000000..2244dd3f --- /dev/null +++ b/src/matchers/toLogError/index.test.js @@ -0,0 +1,30 @@ +import matcher from './'; + +expect.extend(matcher); + +const error = () => console.error('console-error'); // eslint-disable-line +const noop = () => {}; + +describe('.toLogError', () => { + test('passes when the callback function outputs the expected message to the console', () => { + expect(error).toLogError('console-error'); + }); + test('fails when the callback function does not output any message', () => { + expect(() => expect(noop).toLogError('something')).toThrowErrorMatchingSnapshot(); + }); + test('fails when the callback function does not output the expected message', () => { + expect(() => expect(error).toLogError('something')).toThrowErrorMatchingSnapshot(); + }); +}); + +describe('.not.toLogError', () => { + test('passes when the callback function does not output anything', () => { + expect(noop).not.toLogError('something'); + }); + test('passes when the callback function does not output the expected message', () => { + expect(error).not.toLogError('something'); + }); + test('fails when the callback function outputs the expected message', () => { + expect(() => expect(error).not.toLogError('console-error')).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/matchers/toLogError/predicate.js b/src/matchers/toLogError/predicate.js new file mode 100644 index 00000000..70901c0b --- /dev/null +++ b/src/matchers/toLogError/predicate.js @@ -0,0 +1,17 @@ +import { equals } from 'expect/build/jasmine_utils'; + +export default (fn, expected) => { + const error = console.error; // eslint-disable-line no-console + const actual = []; + console.error = (...args) => actual.push(...args); // eslint-disable-line no-console + try { + fn(); + } finally { + console.error = error; // eslint-disable-line no-console + } + if (actual.length < 1) { + return { pass: false }; + } + const pass = actual.some(message => equals(message, expected)); + return { actual, pass }; +}; diff --git a/src/matchers/toLogError/predicate.test.js b/src/matchers/toLogError/predicate.test.js new file mode 100644 index 00000000..e867f2ce --- /dev/null +++ b/src/matchers/toLogError/predicate.test.js @@ -0,0 +1,18 @@ +import predicate from './predicate'; + +const fn = () => console.error('console-error'); // eslint-disable-line + +describe('Predicate > .toLogError', () => { + test('returns an object with pass status and the actual output', () => { + const expected = 'console-error'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(true); + expect(actual).toEqual(['console-error']); + }); + it('returns an object with fail status and the actual output', () => { + const expected = 'something'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(false); + expect(actual).toEqual(['console-error']); + }); +}); diff --git a/src/matchers/toLogInfo/__snapshots__/index.test.js.snap b/src/matchers/toLogInfo/__snapshots__/index.test.js.snap new file mode 100644 index 00000000..3fce9c9b --- /dev/null +++ b/src/matchers/toLogInfo/__snapshots__/index.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.not.toLogInfo fails when the callback function outputs the expected message 1`] = ` +"expect(received).not.toLogInfo(expected) + +Expected console.info not to have been called with: + \\"console-info\\" +But it was called with: + [\\"console-info\\"]" +`; + +exports[`.toLogInfo fails when the callback function does not output any message 1`] = ` +"expect(received).toLogInfo(expected) + +Expected console.info to have been called with: + \\"something\\" +But it was not called" +`; + +exports[`.toLogInfo fails when the callback function does not output the expected message 1`] = ` +"expect(received).toLogInfo(expected) + +Expected console.info to have been called with: + \\"something\\" +But it was called with: + [\\"console-info\\"]" +`; diff --git a/src/matchers/toLogInfo/index.js b/src/matchers/toLogInfo/index.js new file mode 100644 index 00000000..a7b08557 --- /dev/null +++ b/src/matchers/toLogInfo/index.js @@ -0,0 +1,30 @@ +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; + +import predicate from './predicate'; + +const calledWith = actual => + actual ? 'But it was called with:\n' + ` ${printReceived(actual)}` : 'But it was not called'; + +const passMessage = (actual, expected) => () => + matcherHint('.not.toLogInfo') + + '\n\n' + + 'Expected console.info not to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +const failMessage = (actual, expected) => () => + matcherHint('.toLogInfo') + + '\n\n' + + 'Expected console.info to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +export default { + toLogInfo: (fn, expected) => { + const { actual, pass } = predicate(fn, expected); + if (pass) { + return { pass: true, message: passMessage(actual, expected) }; + } + return { pass: false, message: failMessage(actual, expected) }; + } +}; diff --git a/src/matchers/toLogInfo/index.test.js b/src/matchers/toLogInfo/index.test.js new file mode 100644 index 00000000..865542df --- /dev/null +++ b/src/matchers/toLogInfo/index.test.js @@ -0,0 +1,30 @@ +import matcher from './'; + +expect.extend(matcher); + +const info = () => console.info('console-info'); // eslint-disable-line +const noop = () => {}; + +describe('.toLogInfo', () => { + test('passes when the callback function outputs the expected message to the console', () => { + expect(info).toLogInfo('console-info'); + }); + test('fails when the callback function does not output any message', () => { + expect(() => expect(noop).toLogInfo('something')).toThrowErrorMatchingSnapshot(); + }); + test('fails when the callback function does not output the expected message', () => { + expect(() => expect(info).toLogInfo('something')).toThrowErrorMatchingSnapshot(); + }); +}); + +describe('.not.toLogInfo', () => { + test('passes when the callback function does not output anything', () => { + expect(noop).not.toLogInfo('something'); + }); + test('passes when the callback function does not output the expected message', () => { + expect(info).not.toLogInfo('something'); + }); + test('fails when the callback function outputs the expected message', () => { + expect(() => expect(info).not.toLogInfo('console-info')).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/matchers/toLogInfo/predicate.js b/src/matchers/toLogInfo/predicate.js new file mode 100644 index 00000000..1856f86f --- /dev/null +++ b/src/matchers/toLogInfo/predicate.js @@ -0,0 +1,17 @@ +import { equals } from 'expect/build/jasmine_utils'; + +export default (fn, expected) => { + const info = console.info; // eslint-disable-line no-console + const actual = []; + console.info = (...args) => actual.push(...args); // eslint-disable-line no-console + try { + fn(); + } finally { + console.info = info; // eslint-disable-line no-console + } + if (actual.length < 1) { + return { pass: false }; + } + const pass = actual.some(message => equals(message, expected)); + return { actual, pass }; +}; diff --git a/src/matchers/toLogInfo/predicate.test.js b/src/matchers/toLogInfo/predicate.test.js new file mode 100644 index 00000000..807cdafd --- /dev/null +++ b/src/matchers/toLogInfo/predicate.test.js @@ -0,0 +1,18 @@ +import predicate from './predicate'; + +const fn = () => console.info('console-info'); // eslint-disable-line + +describe('Predicate > .toLogInfo', () => { + test('returns an object with pass status and the actual output', () => { + const expected = 'console-info'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(true); + expect(actual).toEqual(['console-info']); + }); + it('returns an object with fail status and the actual output', () => { + const expected = 'something'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(false); + expect(actual).toEqual(['console-info']); + }); +}); diff --git a/src/matchers/toLogWarning/__snapshots__/index.test.js.snap b/src/matchers/toLogWarning/__snapshots__/index.test.js.snap new file mode 100644 index 00000000..a752247e --- /dev/null +++ b/src/matchers/toLogWarning/__snapshots__/index.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.not.toLogWarning fails when the callback function outputs the expected message 1`] = ` +"expect(received).not.toLogWarning(expected) + +Expected console.warn not to have been called with: + \\"console-warning\\" +But it was called with: + [\\"console-warning\\"]" +`; + +exports[`.toLogWarning fails when the callback function does not output any message 1`] = ` +"expect(received).toLogWarning(expected) + +Expected console.warn to have been called with: + \\"something\\" +But it was not called" +`; + +exports[`.toLogWarning fails when the callback function does not output the expected message 1`] = ` +"expect(received).toLogWarning(expected) + +Expected console.warn to have been called with: + \\"something\\" +But it was called with: + [\\"console-warning\\"]" +`; diff --git a/src/matchers/toLogWarning/index.js b/src/matchers/toLogWarning/index.js new file mode 100644 index 00000000..5b551f1e --- /dev/null +++ b/src/matchers/toLogWarning/index.js @@ -0,0 +1,30 @@ +import { matcherHint, printExpected, printReceived } from 'jest-matcher-utils'; + +import predicate from './predicate'; + +const calledWith = actual => + actual ? 'But it was called with:\n' + ` ${printReceived(actual)}` : 'But it was not called'; + +const passMessage = (actual, expected) => () => + matcherHint('.not.toLogWarning') + + '\n\n' + + 'Expected console.warn not to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +const failMessage = (actual, expected) => () => + matcherHint('.toLogWarning') + + '\n\n' + + 'Expected console.warn to have been called with:\n' + + ` ${printExpected(expected)}\n` + + calledWith(actual); + +export default { + toLogWarning: (fn, expected) => { + const { actual, pass } = predicate(fn, expected); + if (pass) { + return { pass: true, message: passMessage(actual, expected) }; + } + return { pass: false, message: failMessage(actual, expected) }; + } +}; diff --git a/src/matchers/toLogWarning/index.test.js b/src/matchers/toLogWarning/index.test.js new file mode 100644 index 00000000..44245a02 --- /dev/null +++ b/src/matchers/toLogWarning/index.test.js @@ -0,0 +1,30 @@ +import matcher from './'; + +expect.extend(matcher); + +const warn = () => console.warn('console-warning'); // eslint-disable-line +const noop = () => {}; + +describe('.toLogWarning', () => { + test('passes when the callback function outputs the expected message to the console', () => { + expect(warn).toLogWarning('console-warning'); + }); + test('fails when the callback function does not output any message', () => { + expect(() => expect(noop).toLogWarning('something')).toThrowErrorMatchingSnapshot(); + }); + test('fails when the callback function does not output the expected message', () => { + expect(() => expect(warn).toLogWarning('something')).toThrowErrorMatchingSnapshot(); + }); +}); + +describe('.not.toLogWarning', () => { + test('passes when the callback function does not output anything', () => { + expect(noop).not.toLogWarning('something'); + }); + test('passes when the callback function does not output the expected message', () => { + expect(warn).not.toLogWarning('something'); + }); + test('fails when the callback function outputs the expected message', () => { + expect(() => expect(warn).not.toLogWarning('console-warning')).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/matchers/toLogWarning/predicate.js b/src/matchers/toLogWarning/predicate.js new file mode 100644 index 00000000..cefc8663 --- /dev/null +++ b/src/matchers/toLogWarning/predicate.js @@ -0,0 +1,17 @@ +import { equals } from 'expect/build/jasmine_utils'; + +export default (fn, expected) => { + const warn = console.warn; // eslint-disable-line no-console + const actual = []; + console.warn = (...args) => actual.push(...args); // eslint-disable-line no-console + try { + fn(); + } finally { + console.warn = warn; // eslint-disable-line no-console + } + if (actual.length < 1) { + return { pass: false }; + } + const pass = actual.some(message => equals(message, expected)); + return { actual, pass }; +}; diff --git a/src/matchers/toLogWarning/predicate.test.js b/src/matchers/toLogWarning/predicate.test.js new file mode 100644 index 00000000..6e7bebcb --- /dev/null +++ b/src/matchers/toLogWarning/predicate.test.js @@ -0,0 +1,18 @@ +import predicate from './predicate'; + +const fn = () => console.warn('console-warn'); // eslint-disable-line + +describe('Predicate > .toLogWarning', () => { + test('returns an object with pass status and the actual output', () => { + const expected = 'console-warn'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(true); + expect(actual).toEqual(['console-warn']); + }); + it('returns an object with fail status and the actual output', () => { + const expected = 'something'; + const { pass, actual } = predicate(fn, expected); + expect(pass).toEqual(false); + expect(actual).toEqual(['console-warn']); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index cd4cc1f4..ec695dec 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -355,6 +355,15 @@ declare namespace jest { * @param {String | RegExp} message */ toThrowWithMessage(type: Function, message: string | RegExp): R; + + /** + * Use `.toLog` when checking if a callback function outputs a message to the console. + * The specified console method is mocked during execution so nothing will be printed to the console. + * + * @param {*} logOutput + * @param {String} [consoleMethod=log] + */ + toLog(logOutput: any, consoleMethod?: string): R; } // noinspection JSUnusedGlobalSymbols