cli-testing-library
Version:
Simple and complete CLI testing utilities that encourage good testing practices.
1 lines • 8.13 kB
Source Map (JSON)
{"version":3,"file":"pure.cjs","sources":["../../src/pure.ts"],"sourcesContent":["import childProcess from \"node:child_process\";\nimport { performance } from \"node:perf_hooks\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport stripFinalNewline from \"strip-final-newline\";\nimport { _runObservers } from \"./mutation-observer\";\nimport { getQueriesForElement } from \"./get-queries-for-instance\";\nimport userEvent from \"./user-event/index\";\nimport { bindObjectFnsToInstance, setCurrentInstance } from \"./helpers\";\nimport { fireEvent } from \"./events\";\nimport { getConfig } from \"./config\";\nimport { logCLI } from \"./pretty-cli\";\nimport type { TestInstance } from \"./types\";\nimport type * as queries from \"./queries/index\";\nimport type { SpawnOptionsWithoutStdio } from \"node:child_process\";\nimport type { BoundFunction } from \"./get-queries-for-instance\";\n\nconst __curDir =\n typeof __dirname === \"undefined\"\n ? // @ts-ignore ESM requires this, but it doesn't work in Node18\n path.dirname(fileURLToPath(import.meta.url))\n : __dirname;\n\nexport interface RenderOptions {\n cwd: string;\n debug: boolean;\n spawnOpts: Omit<SpawnOptionsWithoutStdio, \"cwd\">;\n}\n\ntype UserEvent = typeof userEvent;\n\nexport type RenderResult = TestInstance & {\n userEvent: {\n [P in keyof UserEvent]: BoundFunction<UserEvent[P]>;\n };\n} & { [P in keyof typeof queries]: BoundFunction<(typeof queries)[P]> };\n\nconst mountedInstances = new Set<TestInstance>();\n\nasync function render(\n command: string,\n args: Array<string> = [],\n opts: Partial<RenderOptions> = {},\n): Promise<RenderResult> {\n const { cwd = __curDir, spawnOpts = {} } = opts;\n\n const exec = childProcess.spawn(command, args, {\n ...spawnOpts,\n cwd,\n shell: true,\n });\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n let _readyPromiseInternals: null | { resolve: Function; reject: Function } =\n null;\n\n let _resolved = false;\n\n const execOutputAPI = {\n __exitCode: null as null | number,\n _isOutputAPI: true,\n _isReady: new Promise(\n (resolve, reject) => (_readyPromiseInternals = { resolve, reject }),\n ),\n process: exec,\n // Clear buffer of stdout to do more accurate `t.regex` checks\n clear() {\n execOutputAPI.stdoutArr = [];\n execOutputAPI.stderrArr = [];\n },\n debug(maxLength?: number) {\n logCLI(execOutputAPI, maxLength);\n },\n // An array of strings gathered from stdout when unable to do\n // `await stdout` because of inquirer interactive prompts\n stdoutArr: [] as Array<{ contents: Buffer | string; timestamp: number }>,\n stderrArr: [] as Array<{ contents: Buffer | string; timestamp: number }>,\n hasExit() {\n return this.__exitCode === null ? null : { exitCode: this.__exitCode };\n },\n getStdallStr(): string {\n return this.stderrArr\n .concat(this.stdoutArr)\n .sort((a, b) => (a.timestamp < b.timestamp ? -1 : 1))\n .map((obj) => obj.contents)\n .join(\"\\n\");\n },\n } as TestInstance & {\n __exitCode: null | number;\n _isOutputAPI: true;\n _isReady: Promise<void>;\n };\n\n mountedInstances.add(execOutputAPI as unknown as TestInstance);\n\n exec.stdout.on(\"data\", (result: string | Buffer) => {\n // `on('spawn') doesn't work the same way in Node12.\n // Instead, we have to rely on this working as-expected.\n if (_readyPromiseInternals && !_resolved) {\n _readyPromiseInternals.resolve();\n _resolved = true;\n }\n\n const resStr = stripFinalNewline(result as string);\n execOutputAPI.stdoutArr.push({\n contents: resStr,\n timestamp: performance.now(),\n });\n _runObservers();\n });\n\n exec.stderr.on(\"data\", (result: string | Buffer) => {\n if (_readyPromiseInternals && !_resolved) {\n _readyPromiseInternals.resolve();\n _resolved = true;\n }\n\n const resStr = stripFinalNewline(result as string);\n execOutputAPI.stderrArr.push({\n contents: resStr,\n timestamp: performance.now(),\n });\n _runObservers();\n });\n\n exec.on(\"error\", (result) => {\n if (_readyPromiseInternals) {\n _readyPromiseInternals.reject(result);\n }\n });\n\n exec.on(\"spawn\", () => {\n setTimeout(() => {\n if (_readyPromiseInternals && !_resolved) {\n _readyPromiseInternals.resolve();\n _resolved = true;\n }\n }, getConfig().renderAwaitTime);\n });\n\n exec.on(\"exit\", (code) => {\n execOutputAPI.__exitCode = code ?? 0;\n });\n\n setCurrentInstance(execOutputAPI);\n\n await execOutputAPI._isReady;\n\n function getStdallStr(this: Omit<TestInstance, \"getStdallStr\">) {\n return this.stderrArr\n .concat(this.stdoutArr)\n .sort((a, b) => (a.timestamp < b.timestamp ? -1 : 1))\n .map((obj) => obj.contents)\n .join(\"\\n\");\n }\n\n return Object.assign(\n execOutputAPI,\n {\n userEvent: bindObjectFnsToInstance(execOutputAPI, userEvent as never),\n getStdallStr: getStdallStr.bind(execOutputAPI),\n },\n getQueriesForElement(execOutputAPI),\n ) as TestInstance as RenderResult;\n}\n\nfunction cleanup() {\n return Promise.all(Array.from(mountedInstances).map(cleanupAtInstance));\n}\n\n// maybe one day we'll expose this (perhaps even as a utility returned by render).\n// but let's wait until someone asks for it.\nasync function cleanupAtInstance(instance: TestInstance) {\n await fireEvent.sigkill(instance);\n\n mountedInstances.delete(instance);\n}\n\nexport { render, cleanup };\n"],"names":["fileURLToPath","logCLI","performance","_runObservers","getConfig","setCurrentInstance","bindObjectFnsToInstance","userEvent","getQueriesForElement","fireEvent"],"mappings":"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"}