diff --git a/src/matches.js b/src/matches.js index ec7a44a..b478849 100644 --- a/src/matches.js +++ b/src/matches.js @@ -1,3 +1,5 @@ +import stripAnsiFn from 'strip-ansi' + function assertNotNullOrUndefined(matcher) { if (matcher === null || matcher === undefined) { throw new Error( @@ -43,33 +45,38 @@ function matches(textToMatch, node, matcher, normalizer) { } } -function getDefaultNormalizer({trim = true, collapseWhitespace = true} = {}) { +function getDefaultNormalizer({trim = true, collapseWhitespace = true, stripAnsi = true} = {}) { return text => { let normalizedText = text normalizedText = trim ? normalizedText.trim() : normalizedText normalizedText = collapseWhitespace ? normalizedText.replace(/\s+/g, ' ') : normalizedText + normalizedText = stripAnsi ? stripAnsiFn(normalizedText) : normalizedText return normalizedText } } /** + * @param {Object} props * Constructs a normalizer to pass to functions in matches.js - * @param {boolean|undefined} trim The user-specified value for `trim`, without + * @param {boolean|undefined} props.trim The user-specified value for `trim`, without + * any defaulting having been applied + * @param {boolean|undefined} props.stripAnsi The user-specified value for `stripAnsi`, without * any defaulting having been applied - * @param {boolean|undefined} collapseWhitespace The user-specified value for + * @param {boolean|undefined} props.collapseWhitespace The user-specified value for * `collapseWhitespace`, without any defaulting having been applied - * @param {Function|undefined} normalizer The user-specified normalizer + * @param {Function|undefined} props.normalizer The user-specified normalizer * @returns {Function} A normalizer */ -function makeNormalizer({trim, collapseWhitespace, normalizer}) { +function makeNormalizer({trim, stripAnsi, collapseWhitespace, normalizer}) { if (normalizer) { // User has specified a custom normalizer if ( typeof trim !== 'undefined' || - typeof collapseWhitespace !== 'undefined' + typeof collapseWhitespace !== 'undefined' || + typeof stripAnsi !== 'undefined' ) { // They've also specified a value for trim or collapseWhitespace throw new Error( @@ -82,7 +89,7 @@ function makeNormalizer({trim, collapseWhitespace, normalizer}) { return normalizer } else { // No custom normalizer specified. Just use default. - return getDefaultNormalizer({trim, collapseWhitespace}) + return getDefaultNormalizer({trim, collapseWhitespace, stripAnsi}) } } diff --git a/src/pure.ts b/src/pure.ts index 1c4a990..ea907ff 100644 --- a/src/pure.ts +++ b/src/pure.ts @@ -6,25 +6,10 @@ */ import childProcess from 'child_process' import stripFinalNewline from 'strip-final-newline' -import stripAnsi from 'strip-ansi' import {RenderOptions, TestInstance} from '../types/pure' import {_runObservers} from './mutation-observer' import {getQueriesForElement} from './get-queries-for-instance' -class NotBoundToInstanceError extends Error { - constructor(prop: string) { - super(` - You've attempted to read '${prop}' from a destructured value. - Please do not destructure \`${prop}\` from \`render\`, instead do something like this: - - const client = render( /* ... */ ); - expect(client.${prop}).toBe(["Hi"]); - - Because ${prop} relies on mutation to function, you'll be left with stale data if this is not done - `) - } -} - async function render( command: string, args: string[] = [], @@ -40,6 +25,8 @@ async function render( let _readyPromiseInternals: null | {resolve: Function; reject: Function} = null + let _isReadyResolved = false; + const execOutputAPI = { _isOutputAPI: true, _isReady: new Promise( @@ -51,42 +38,35 @@ async function render( }, // An array of strings gathered from stdout when unable to do // `await stdout` because of inquirer interactive prompts - _stdoutArr: [] as string[], - get stdoutArr(): string[] { - // TODO: This error throwing doesn't _actually_ work, because - // when the value is initially destructured, `this`, _is_ defined - // and later, when the user goes to run `console.log(stdoutArr`), it's no - // longer referencing this getter - instead it's a new variable that's assigned - // a non-getter string - if (!(this as unknown) || !this._isOutputAPI) { - throw new NotBoundToInstanceError('stdoutArr') - } - return this._stdoutArr - }, - set stdoutArr(val: string[]) {}, + stdoutArr: [] as Array, get stdoutStr(): string { - if (!(this as unknown) || !this._isOutputAPI) { - throw new NotBoundToInstanceError('stdoutStr') - } return this.stdoutArr.join('\n') }, set stdoutStr(val: string) {}, } exec.stdout.on('data', (result: string | Buffer) => { - // TODO: Move `strip-ansi` to `normalizer` within `queries-text` instead - const resStr = stripAnsi(stripFinalNewline(result as string).toString()) + const resStr = stripFinalNewline(result as string); execOutputAPI.stdoutArr.push(resStr) _runObservers() - if (_readyPromiseInternals) _readyPromiseInternals.resolve() + if (_readyPromiseInternals && !_isReadyResolved) { + _readyPromiseInternals.resolve() + _isReadyResolved = true; + } }) exec.stdout.on('error', result => { - if (_readyPromiseInternals) _readyPromiseInternals.reject(result) + if (_readyPromiseInternals && !_isReadyResolved) { + _readyPromiseInternals.reject(result) + _isReadyResolved = true; + } }) exec.stderr.on('data', (result: string) => { - if (_readyPromiseInternals) _readyPromiseInternals.reject(new Error(result)) + if (_readyPromiseInternals && !_isReadyResolved) { + _readyPromiseInternals.reject(new Error(result)) + _isReadyResolved = true; + } }) await execOutputAPI._isReady diff --git a/src/queries/text.ts b/src/queries/text.ts index ca1fe04..053a96e 100644 --- a/src/queries/text.ts +++ b/src/queries/text.ts @@ -13,16 +13,17 @@ const queryByTextBase: QueryByText = ( exact = false, collapseWhitespace, trim, - normalizer + normalizer, + stripAnsi } = {}, ) => { const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({ + stripAnsi, collapseWhitespace, trim, - normalizer, - // Why TypeScript, why? - } as unknown as true) + normalizer + }) const str = instance.stdoutStr if (matcher(str, instance, text, matchNormalizer)) return instance else return null diff --git a/types/matches.d.ts b/types/matches.d.ts index 6020464..531fb75 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -15,6 +15,8 @@ export interface MatcherOptions { /** Use normalizer with getDefaultNormalizer instead */ trim?: boolean /** Use normalizer with getDefaultNormalizer instead */ + stripAnsi?: boolean + /** Use normalizer with getDefaultNormalizer instead */ collapseWhitespace?: boolean normalizer?: NormalizerFn /** suppress suggestions for a specific query */ @@ -31,6 +33,7 @@ export type Match = ( export interface DefaultNormalizerOptions { trim?: boolean collapseWhitespace?: boolean + stripAnsi?: boolean } export function getDefaultNormalizer( diff --git a/types/pure.d.ts b/types/pure.d.ts index e7e8109..0f53175 100644 --- a/types/pure.d.ts +++ b/types/pure.d.ts @@ -10,8 +10,7 @@ export interface RenderOptions { */ export interface TestInstance { cleanup(): void - // Possibly switch to `stdout.on('data'` prop in the future - stdoutArr: string[] + stdoutArr: Array stdin: Writable stdout: Readable stderr: Readable