Skip to content

Commit

Permalink
Migrate ANSI strip to normalizer
Browse files Browse the repository at this point in the history
  • Loading branch information
crutchcorn committed Nov 8, 2021
1 parent 709cf62 commit c8bb37d
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 49 deletions.
21 changes: 14 additions & 7 deletions src/matches.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import stripAnsiFn from 'strip-ansi'

function assertNotNullOrUndefined(matcher) {
if (matcher === null || matcher === undefined) {
throw new Error(
Expand Down Expand Up @@ -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(
Expand All @@ -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})
}
}

Expand Down
52 changes: 16 additions & 36 deletions src/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [],
Expand All @@ -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(
Expand All @@ -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<string | Buffer>,
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
Expand Down
9 changes: 5 additions & 4 deletions src/queries/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions types/matches.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -31,6 +33,7 @@ export type Match = (
export interface DefaultNormalizerOptions {
trim?: boolean
collapseWhitespace?: boolean
stripAnsi?: boolean
}

export function getDefaultNormalizer(
Expand Down
3 changes: 1 addition & 2 deletions types/pure.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Buffer>
stdin: Writable
stdout: Readable
stderr: Readable
Expand Down

0 comments on commit c8bb37d

Please sign in to comment.