├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── Bug_Report.md │ ├── Feature_Request.md │ └── Question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── autofix.yml │ ├── ci.yml │ ├── deploy-preview.yml │ └── pr.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── LICENSE ├── README.md ├── docs ├── api.md ├── configure.md ├── debug.md ├── differences.md ├── fire-event.md ├── introduction.md ├── matchers.md ├── queries.md ├── reference │ ├── classes │ │ └── mutationobserver.md │ ├── functions │ │ ├── bindobjectfnstoinstance.md │ │ ├── buildqueries.md │ │ ├── cleanup.md │ │ ├── configure.md │ │ ├── debounce.md │ │ ├── fireevent.md │ │ ├── getconfig.md │ │ ├── getcurrentinstance.md │ │ ├── getdefaultnormalizer.md │ │ ├── getinstanceerror.md │ │ ├── getqueriesforelement.md │ │ ├── jestfaketimersareenabled.md │ │ ├── makefindquery.md │ │ ├── makenormalizer.md │ │ ├── render.md │ │ ├── runobservers.md │ │ ├── runwithexpensiveerrordiagnosticsdisabled.md │ │ ├── setcurrentinstance.md │ │ ├── waitfor.md │ │ └── wrapsinglequerywithsuggestion.md │ ├── index.md │ ├── interfaces │ │ ├── config.md │ │ ├── configfn.md │ │ ├── defaultnormalizeroptions.md │ │ ├── keyboardkey.md │ │ ├── matcheroptions.md │ │ ├── normalizeroptions.md │ │ ├── queries.md │ │ ├── renderoptions.md │ │ ├── selectormatcheroptions.md │ │ └── waitforoptions.md │ ├── namespaces │ │ └── queries │ │ │ ├── functions │ │ │ ├── findbyerror.md │ │ │ ├── findbytext.md │ │ │ ├── getbyerror.md │ │ │ ├── getbytext.md │ │ │ ├── querybyerror.md │ │ │ └── querybytext.md │ │ │ ├── index.md │ │ │ └── type-aliases │ │ │ ├── findbyerror.md │ │ │ ├── findbytext.md │ │ │ ├── getbyerror.md │ │ │ ├── getbytext.md │ │ │ ├── querybyerror.md │ │ │ └── querybytext.md │ └── type-aliases │ │ ├── boundfunction.md │ │ ├── boundfunctions.md │ │ ├── eventtype.md │ │ ├── firefunction.md │ │ ├── fireobject.md │ │ ├── geterrorfunction.md │ │ ├── match.md │ │ ├── matcher.md │ │ ├── matcherfunction.md │ │ ├── normalizerfn.md │ │ ├── query.md │ │ ├── querymethod.md │ │ ├── renderresult.md │ │ └── withsuggest.md └── user-event.md ├── eslint.config.js ├── knip.json ├── media └── koala.png ├── nx.json ├── package.json ├── packages └── cli-testing-library │ ├── README.md │ ├── eslint.config.js │ ├── jest-globals │ └── package.json │ ├── jest │ └── package.json │ ├── package.json │ ├── src │ ├── config.ts │ ├── event-map.ts │ ├── events.ts │ ├── get-queries-for-instance.ts │ ├── get-user-code-frame.ts │ ├── helpers.ts │ ├── index.ts │ ├── jest-globals.ts │ ├── jest.ts │ ├── matchers │ │ ├── index.ts │ │ ├── to-be-in-the-console.ts │ │ ├── to-have-errormessage.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── matches.ts │ ├── mutation-observer.ts │ ├── pretty-cli.ts │ ├── process-helpers.ts │ ├── pure.ts │ ├── queries │ │ ├── all-utils.ts │ │ ├── error.ts │ │ ├── index.ts │ │ └── text.ts │ ├── query-helpers.ts │ ├── suggestions.ts │ ├── types.ts │ ├── user-event │ │ ├── index.ts │ │ ├── keyboard │ │ │ ├── getNextKeyDef.ts │ │ │ ├── index.ts │ │ │ ├── keyMap.ts │ │ │ ├── keyboardImplementation.ts │ │ │ └── types.ts │ │ └── utils.ts │ ├── vitest.ts │ └── wait-for.ts │ ├── tests │ ├── events.spec.ts │ ├── execute-scripts │ │ ├── list-args.js │ │ ├── log-err.js │ │ ├── log-output.js │ │ ├── stdio-inquirer-input.js │ │ ├── stdio-inquirer.js │ │ └── throw.js │ ├── get-user-code-frame.spec.ts │ ├── matchers.spec.ts │ ├── matches.spec.ts │ ├── pretty-cli.spec.ts │ ├── queries.spec.ts │ ├── render-basics.spec.ts │ ├── setup.ts │ ├── user-keyboard.spec.ts │ └── wait-for.spec.ts │ ├── tsconfig.docs.json │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest │ └── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── generateDocs.js ├── publish.js └── tsconfig.json ├── tsconfig.json └── website ├── .gitignore ├── astro.config.mjs ├── package.json ├── public ├── koala.png └── share-banner.png ├── src ├── components │ └── head.astro ├── content.config.ts ├── content │ └── docs │ │ ├── guides │ │ └── index.mdx └── styles │ └── global.css └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [crutchcorn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | - `cli-testing-library` version: 12 | - Testing Framework and version: 13 | 14 | - DOM Environment: 15 | 16 | 17 | 22 | 23 | Relevant code or config 24 | 25 | ```js 26 | 27 | ``` 28 | 29 | What you did: 30 | 31 | What happened: 32 | 33 | 34 | 35 | Reproduction repository: 36 | 37 | 41 | 42 | Problem description: 43 | 44 | Suggested solution: 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Bugs, missing documentation, or unexpected behavior 🤔. 4 | --- 5 | 6 | 15 | 16 | - `cli-testing-library` version: 17 | - Testing Framework and version: 18 | 19 | - DOM Environment: 20 | 21 | 22 | 27 | 28 | ### Relevant code or config: 29 | 30 | ```js 31 | var your => (code) => here; 32 | ``` 33 | 34 | ### What you did: 35 | 36 | 37 | 38 | ### What happened: 39 | 40 | 41 | 42 | ### Reproduction: 43 | 44 | 48 | 49 | ### Problem description: 50 | 51 | 52 | 53 | ### Suggested solution: 54 | 55 | 59 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature Request 3 | about: I have a suggestion (and might want to implement myself 🙂)! 4 | --- 5 | 6 | 21 | 22 | ### Describe the feature you'd like: 23 | 24 | 28 | 29 | ### Suggested implementation: 30 | 31 | 32 | 33 | ### Describe alternatives you've considered: 34 | 35 | 39 | 40 | ### Teachability, Documentation, Adoption, Migration Strategy: 41 | 42 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Support Question 3 | about: 🛑 If you have a question 💬, please check out our support channels! 4 | --- 5 | 6 | ------------ 👆 Click "Preview"! 7 | 8 | Issues on GitHub are intended to be related to problems with the library itself 9 | and feature requests so we recommend not using this medium to ask them here 😁. 10 | 11 | --- 12 | 13 | **ISSUES WHICH ARE QUESTIONS WILL BE CLOSED** 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | **What**: 17 | 18 | 19 | 20 | **Why**: 21 | 22 | 23 | 24 | **How**: 25 | 26 | 27 | 28 | **Checklist**: 29 | 30 | 31 | 32 | 33 | 34 | - [ ] Tests 35 | - [ ] TypeScript definitions updated 36 | - [ ] Ready to be merged 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci # needed to securely identify the workflow 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main, alpha, beta] 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | autofix: 17 | name: autofix 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4.2.2 22 | - name: Setup Tools 23 | uses: tanstack/config/.github/setup@main 24 | - name: Fix formatting 25 | run: pnpm prettier:write 26 | - name: Generate Docs 27 | run: pnpm docs:generate 28 | - name: Apply fixes 29 | uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c 30 | with: 31 | commit-message: "ci: apply automated fixes and generate docs" 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: override release tag 8 | required: false 9 | push: 10 | branches: [main, alpha, beta] 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 14 | cancel-in-progress: true 15 | 16 | permissions: 17 | contents: write 18 | id-token: write 19 | 20 | jobs: 21 | test-and-publish: 22 | name: Test & Publish 23 | if: github.repository == 'crutchcorn/cli-testing-library' 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - name: Setup Tools 29 | uses: tanstack/config/.github/setup@main 30 | - name: Run Tests 31 | run: pnpm run test:ci --parallel=3 32 | - name: Publish 33 | run: | 34 | git config --global user.name 'Corbin Crutchley' 35 | git config --global user.email 'git@crutchcorn.dev' 36 | npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" 37 | pnpm run cipublish 38 | env: 39 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | TAG: ${{ inputs.tag }} 42 | -------------------------------------------------------------------------------- /.github/workflows/deploy-preview.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Preview 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | deploy-docs: 12 | permissions: 13 | contents: write # to write to gh-pages branch (peaceiris/actions-gh-pages) 14 | 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version-file: ".nvmrc" 23 | cache: "pnpm" 24 | 25 | - name: Install packages 26 | run: pnpm install --frozen-lockfile --prefer-offline 27 | 28 | - name: Build 29 | run: pnpm run build:website 30 | 31 | - name: Deploy docs 32 | uses: peaceiris/actions-gh-pages@v4 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: website/dist 36 | cname: cli-testing.com 37 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "docs/**" 7 | - "media/**" 8 | - "**/*.md" 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | test: 19 | name: Test 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup Tools 25 | uses: tanstack/config/.github/setup@main 26 | - name: Run Checks 27 | run: pnpm run test:pr --parallel=3 28 | preview: 29 | name: Preview 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | - name: Setup Tools 37 | uses: tanstack/config/.github/setup@main 38 | - name: Build Packages 39 | run: pnpm run build:all 40 | - name: Publish Previews 41 | run: pnpx pkg-pr-new publish --pnpm --compact './packages/*' --template './examples/*/*' 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | package-lock.json 7 | yarn.lock 8 | 9 | # builds 10 | build 11 | coverage 12 | dist 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .next 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .history 27 | size-plugin.json 28 | stats-hydration.json 29 | stats.json 30 | stats.html 31 | .vscode/settings.json 32 | 33 | *.log 34 | .cache 35 | .idea 36 | .nx/cache 37 | .nx/workspace-data 38 | .pnpm-store 39 | .tsup 40 | 41 | vite.config.js.timestamp-* 42 | vite.config.ts.timestamp-* 43 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | link-workspace-packages=true 2 | prefer-workspace-packages=true 3 | provenance=true 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.12.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.next 2 | **/.nx/cache 3 | **/.svelte-kit 4 | **/build 5 | **/coverage 6 | **/dist 7 | **/docs 8 | **/codemods/**/__testfixtures__ 9 | pnpm-lock.yaml 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2021 Corbin Crutchley 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/cli-testing-library/README.md -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "API" 3 | --- 4 | 5 | `CLI Testing Library`, despite taking clear inspiration from, does not re-export 6 | anything from 7 | [`DOM Testing Library`](https://testing-library.com/docs/dom-testing-library/). 8 | Likewise, while we've done our best to match the API names of 9 | [Testing Library's Core API](https://testing-library.com/docs/), because of the 10 | inherent differences between CLI apps and web apps, we're unable to match all of 11 | them. 12 | 13 | > Know of a Testing Library Core API that you think would fit here that isn't 14 | > present? 15 | > [Let us know!](https://github.com/crutchcorn/cli-testing-library/issues) 16 | 17 | Instead, the following API is what `CLI Testing Library` provides the following. 18 | 19 | # `render` 20 | 21 | ```typescript 22 | function render( 23 | command: string, 24 | args: string[], 25 | options?: { 26 | /* You won't often use this, expand below for docs on options */ 27 | }, 28 | ): RenderResult 29 | ``` 30 | 31 | Run the CLI application in a newly spawned process. 32 | 33 | ```javascript 34 | import {render} from 'cli-testing-library' 35 | 36 | render('node', ['./path/to/script.js']) 37 | ``` 38 | 39 | ```javascript 40 | import {render} from 'cli-testing-library' 41 | 42 | test('renders a message', () => { 43 | const {getByText} = render('node', ['./console-out.js']) 44 | expect(getByText('Hello, world!')).toBeTruthy() 45 | }) 46 | ``` 47 | 48 | # `render` Options 49 | 50 | You won't often need to specify options, but if you ever do, here are the 51 | available options which you could provide as a third argument to `render`. 52 | 53 | ## `cwd` 54 | 55 | By default, `CLI Testing Library` will run the new process in the working 56 | directory of your project's root, as defined by your testing framework. If you 57 | provide your own working directory via this option, it will change the execution 58 | directory of your process. 59 | 60 | For example: If you are end-to-end testing a file moving script, you don't want 61 | to have to specify the absolute path every time. In this case, you can specify a 62 | directory as the render `cwd`. 63 | 64 | ```javascript 65 | const containingPath = path.resolve(__dirname, './movables') 66 | 67 | const {getByText} = render('node', ['mover.js'], { 68 | cwd: containingPath, 69 | }) 70 | ``` 71 | 72 | ## `spawnOpts` 73 | 74 | Oftentimes, you want to modify the behavior of the spawn environment. This may 75 | include things like changing the shell that's used to run scripts or more. 76 | 77 | This argument allows you to configure the options that are passed to the 78 | underlying 79 | [`child_process.spawn` NodeJS API](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options). 80 | 81 | ```javascript 82 | const {getByText} = render('script.ps1', { 83 | spawnOpts: { 84 | shell: 'powershell.exe', 85 | }, 86 | }) 87 | ``` 88 | 89 | # `render` Result 90 | 91 | The `render` method returns an object that has a few properties: 92 | 93 | ## `...queries` 94 | 95 | The most important feature of render is that the queries from 96 | [CLI Testing Library](https://github.com/crutchcorn/cli-testing-library) are 97 | automatically returned with their first argument bound to the testInstance. 98 | 99 | See [Queries](./queries) to learn more about how to use these queries and the 100 | philosophy behind them. 101 | 102 | ### ByText 103 | 104 | > getByText, queryByText, findByText 105 | 106 | ```typescript 107 | getByText( 108 | // If you're using `screen`, then skip the container argument: 109 | instance: TestInstance, 110 | text: TextMatch, 111 | options?: { 112 | exact?: boolean = true, 113 | trim?: boolean = false, 114 | stripAnsi?: boolean = false, 115 | collapseWhitespace?: boolean = false, 116 | normalizer?: NormalizerFn, 117 | suggest?: boolean, 118 | }): TestInstance 119 | ``` 120 | 121 | Queries for test instance `stdout` results with the given text (and it also 122 | accepts a TextMatch). 123 | 124 | These options are all standard for text matching. To learn more, see our 125 | [Queries page](./queries). 126 | 127 | ## `userEvent[eventName]` 128 | 129 | ```javascript 130 | userEvent[eventName](...eventProps) 131 | ``` 132 | 133 | > While `userEvent` isn't usually returned on `render` in, say, 134 | > `React Testing Library`, we're able to do so because of our differences in 135 | > implementation with upstream. See our [Differences](./differences) doc for 136 | > more. 137 | 138 | This object is the same as described with 139 | [`userEvent` documentation](./user-event) with the key difference that 140 | `instance` is not expected to be passed when bound to `render`. 141 | 142 | ## `debug` 143 | 144 | This method is a shortcut for `console.log(prettyCLI(instance)).` 145 | 146 | ```javascript 147 | import {render} from 'cli-testing-library' 148 | 149 | const {debug} = render('command') 150 | debug() 151 | // Hello, world! How are you? 152 | // 153 | // you can also pass an instance: debug(getByText('message')) 154 | // and you can pass all the same arguments to debug as you can 155 | // to prettyCLI: 156 | // const maxLengthToPrint = 10000 157 | // debug(getByText('message'), maxLengthToPrint) 158 | ``` 159 | 160 | This is a simple wrapper around `prettyCLI` which is also exposed and comes from 161 | [CLI Testing Library](./debug). 162 | 163 | ## `hasExit` 164 | 165 | This method allows you to check if the spawned process has exit, and if so, what 166 | exit code it closed with. 167 | 168 | ```javascript 169 | const instance = render('command') 170 | 171 | await waitFor(() => expect(instance.hasExit()).toMatchObject({exitCode: 1})) 172 | ``` 173 | 174 | This method returns `null` if still running, but `{exitCode: number}` if it has 175 | exit 176 | 177 | ## `process` 178 | 179 | The spawned process created by your rendered `TestInstnace`. It's a 180 | `child_instance`. This is a 181 | [regularly spawned process](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options), 182 | so you can call `process.pid` etc. to inspect the process. 183 | 184 | ## `stdoutArr`/`stderrArr` 185 | 186 | Each of these is an array of what's output by their respective `std`\* pipe. 187 | This is used internally to create the [`debug`methods](./debug) and more. 188 | They're defined as: 189 | 190 | ```typescript 191 | interface TestInstance { 192 | stdoutArr: Array<{contents: Buffer | string; timestamp: number}> 193 | stderrArr: Array<{contents: Buffer | string; timestamp: number}> 194 | } 195 | ``` 196 | 197 | ## `clear` 198 | 199 | This method acts as the terminal `clear` command might on most systems. It 200 | allows you to clear out the buffers for `stdoutArr` and `stderrArr` - even in 201 | the middle of processing - in order to do more narrowed queries. 202 | 203 | # `cleanup` 204 | 205 | `SIGKILL`s processes that were spawned with render and have not halted by the 206 | end of the test. 207 | 208 | > Please note that this is done automatically if the testing framework you're 209 | > using supports the `afterEach` global and it is injected to your testing 210 | > environment (like mocha, Jest, Vitest, and Jasmine). If not, you will need to do 211 | > manual cleanups after each test. 212 | 213 | For example, if you're using the [ava](https://github.com/avajs/ava) testing 214 | framework, then you would need to use the `test.afterEach` hook like so: 215 | 216 | ```javascript 217 | import {cleanup, render} from 'cli-testing-library' 218 | import test from 'ava' 219 | 220 | test.afterEach(cleanup) 221 | 222 | test('renders into document', () => { 223 | render('long-running-command') 224 | // ... 225 | }) 226 | 227 | // ... more tests ... 228 | ``` 229 | 230 | Failing to call cleanup when you've called render could result in Jest failing 231 | to close due to unclosed handles and tests which are not "idempotent" (which can 232 | lead to difficult to debug errors in your tests). 233 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Configuration Options" 3 | --- 4 | 5 | ## Introduction 6 | 7 | The library can be configured via the `configure` function, which accepts: 8 | 9 | - a plain JS object; this will be merged into the existing configuration. e.g. 10 | `configure({asyncUtilTimeout: 800})` 11 | - a function; the function will be given the existing configuration, and should 12 | return a plain JS object which will be merged as above, e.g. 13 | `configure(existingConfig => ({something: [...existingConfig.something, 'extra value for the something array']}))` 14 | 15 | ## Options 16 | 17 | ### `showOriginalStackTrace` 18 | 19 | By default, `waitFor` will ensure that the stack trace for errors thrown by 20 | Testing Library is cleaned up and shortened so it's easier for you to identify 21 | the part of your code that resulted in the error (async stack traces are hard to 22 | debug). If you want to disable this, then set`showOriginalStackTrace` to 23 | `false`. You can also disable this for a specific call in the options you pass 24 | to `waitFor`. 25 | 26 | ### `throwSuggestions` (experimental) 27 | 28 | When enabled, if [better queries](./queries) are available the test will fail 29 | and provide a suggested query to use instead. Default to `false`. 30 | 31 | To disable a suggestion for a single query just add `{suggest:false}` as an 32 | option. 33 | 34 | ```js 35 | getByText('foo', {suggest: false}) // will not throw a suggestion 36 | ``` 37 | 38 | ### `getInstanceError` 39 | 40 | A function that returns the error used when 41 | [get or find queries](./queries.md#types-of-queries) fail. Takes the error 42 | message and `TestInstance` object as arguments. 43 | 44 | ### `asyncUtilTimeout` 45 | 46 | The global timeout value in milliseconds used by `waitFor` utilities. Defaults 47 | to 1000ms. 48 | 49 | ### `renderAwaitTime` 50 | 51 | By default, we wait for the CLI to `spawn` the command from `render`. If we 52 | immediately resolve the promise to allow users to query, however, we lose the 53 | ability to `getByText` immediately after rendering. This 54 | [differs greatly from upstream Testing Library](./differences) and makes for 55 | a poor testing experience. 56 | 57 | As a result, we wait this duration before resolving the promise after the 58 | process is spawned. This gives runtimes like NodeJS time to spin up and execute 59 | commands. 60 | 61 | Defaults to 100ms. 62 | -------------------------------------------------------------------------------- /docs/debug.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Debug" 3 | --- 4 | 5 | ## Automatic Logging 6 | 7 | When you use any `get` calls in your test cases, the current state of the 8 | `testInstance` (CLI) gets printed on the console. For example: 9 | 10 | ```javascript 11 | // Hello world 12 | getByText(container, 'Goodbye world') // will fail by throwing error 13 | ``` 14 | 15 | The above test case will fail, however it prints the state of your DOM under 16 | test, so you will get to see: 17 | 18 | ``` 19 | Unable to find an element with the text: Goodbye world. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. 20 | Here is the state of your container: 21 | 22 | Hello world 23 | ``` 24 | 25 | Note: Since the CLI size can get really large, you can set the limit of CLI 26 | content to be printed via environment variable `DEBUG_PRINT_LIMIT`. The default 27 | value is `7000`. You will see `...` in the console, when the CLI content is 28 | stripped off, because of the length you have set or due to default size limit. 29 | Here's how you might increase this limit when running tests: 30 | 31 | ``` 32 | DEBUG_PRINT_LIMIT=10000 npm test 33 | ``` 34 | 35 | This works on macOS/Linux, you'll need to do something else for Windows. If 36 | you'd like a solution that works for both, see 37 | [`cross-env`](https://www.npmjs.com/package/cross-env). 38 | 39 | ## `prettyCLI` 40 | 41 | Built on top of [`strip-ansi`](https://github.com/chalk/strip-ansi) this helper 42 | function can be used to print out readable representation of the CLI `stdout` of 43 | a process. This can be helpful for instance when debugging tests. 44 | 45 | It is defined as: 46 | 47 | ```typescript 48 | function prettyDOM(instance: TestInstance, maxLength?: number): string 49 | ``` 50 | 51 | It receives the `TestInstance` to print out, an optional extra parameter to 52 | limit the size of the resulting string, for cases when it becomes too large. 53 | 54 | This function is usually used alongside `console.log` to temporarily print out 55 | CLI outputs during tests for debugging purposes: 56 | 57 | This function is what also powers 58 | [the automatic debugging output described above](#debugging). 59 | -------------------------------------------------------------------------------- /docs/differences.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Differences Between CLI Testing Library & Testing Library" 3 | --- 4 | 5 | While we clearly take inspiration from 6 | [`DOM Testing Library`](https://github.com/testing-library/dom-testing-library) 7 | and 8 | [`React Testing Library`](https://github.com/testing-library/react-testing-library), 9 | and try to do our best to align as much as possible, there are some major 10 | distinctions between the project's APIs. 11 | 12 | # Instances 13 | 14 | While the `DOM Testing Library` and it's descendants have a concept of both a 15 | `container` and `element`, `CLI Testing Library` only has a single concept to 16 | replace them both: a `TestInstance`. 17 | 18 | This is because, while the DOM makes a clear distinction between different 19 | elements (say, 20 | [`HTMLBodyElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLBodyElement) 21 | and 22 | [`HTMLDivElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement)), 23 | there is no such distinction between processes in the CLI as far as I'm aware. 24 | 25 | While this may change in the future (maybe `container` can be a sort of 26 | `TestProcess` of some kind), there are 27 | [unanswered questions around what that API looks like and whether we can meaningfully handle the distinction](https://github.com/crutchcorn/cli-testing-library/issues/2). 28 | 29 | # Queries 30 | 31 | Similarly, `HTMLElement`s provide clean lines between user input items. 32 | Meanwhile, the CLI only has the concepts of `stdin`, `stdout`, and `stderr`. 33 | It's unclear how matching a single line of `stdout` would help in a beneficial 34 | matter, much less how APIs like `fireEvent` would utilize this seemingly 35 | extraneous metadata. 36 | 37 | As a result, we don't have any `*AllBy` queries that `DOM Testing Library` does. 38 | All of our queries are non-plural (`*By`) and will simply return either the test 39 | instance the query was called on, or `null` depending on the query. 40 | 41 | While we would be happy to reconsider, there are once again 42 | [lots of questions around what `*AllBy` queries would like and whether we can meaningfully use that concept](https://github.com/crutchcorn/cli-testing-library/issues/2). 43 | 44 | # Events 45 | 46 | Another area where we diverge from the DOM is in our event system (as 47 | implemented via `fireEvent` and `userEvent`). 48 | 49 | Despite our APIs' naming indicating an actual event system (complete with 50 | bubbling and more, like the DOM), the CLI has no such concept. 51 | 52 | ### FireEvent 53 | 54 | This means that, while `fireEvent` in the `DOM Testing Library` has the ability 55 | to inherent from all baked-into-browser events, we must hard-code a list of 56 | "events" and actions a user may take. 57 | 58 | We try our best to implement ones that make sense: 59 | 60 | - `fireEvent.write({value: str})` to write `str` into stdin 61 | - `fireEvent.sigkill()` to send a 62 | [sigkill]() to the process 63 | - `fireEvent.sigint()` to send a 64 | [sigint]() to the process 65 | 66 | There is a missing API for that might make sense in `keypress`. It's unclear 67 | what this behavior would do that `write` wouldn't be able to. 68 | 69 | ### UserEvent 70 | 71 | There's also the API of `userEvent` that allows us to implement a fairly similar 72 | `keyboard` event 73 | [to upstream](https://testing-library.com/docs/ecosystem-user-event/#keyboardtext-options). 74 | However, there are a few differences here as well: 75 | 76 | 1. `userEvent` can be bound to the `render` output. This is due to limitations 77 | of not having a `screen` API. It's unclear how this would work in practice, 78 | due to a lack of the `window` API. 79 | 2. `userEvent.keyboard` does not support modifier keys. It's only key-presses, 80 | no holding. This is because of API differences that would require us to 81 | figure out a manual binding system for every single key using ANSI AFAIK. 82 | 3. Relatedly, we've remove the `{` syntax support, since there is no standard 83 | keybinding for the CLI, 84 | [like there is for the DOM](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code). 85 | Instead, we only support `[` and `]` syntax. 86 | 87 | # Matchers 88 | 89 | While most of this document has been talking about `DOM Testing Library` and 90 | `React Testing Library`, let's take a moment to talk about 91 | [`jest-dom`](https://github.com/testing-library/jest-dom) matchers. 92 | 93 | Usually, in a Testing Library testbed, you'd expect a `getByText` to look 94 | something like this: 95 | 96 | ```javascript 97 | const {getByText} = render(/* Something */) 98 | 99 | expect(getByText('Hello World')).toBeInTheDocument() 100 | ``` 101 | 102 | In today's version of `CLI Testing Library`, the same would look something like 103 | this: 104 | 105 | ```javascript 106 | const {getByText} = render(/* Something */) 107 | 108 | expect(getByText('Hello World')).toBeInTheConsole() 109 | ``` 110 | 111 | # Similarities 112 | 113 | None of this is to say that we're not dedicated to being aligned with upstream. 114 | We would love to work with the broader Testing Library community to figure out 115 | these problems and adjust our usage. 116 | 117 | This is the primary reason the library is still published as an `@alpha`, 118 | despite being stable enough to successfully power 119 | [a popular CLI application](https://github.com/plopjs/plop/)'s testbed. 120 | 121 | What's more, we're dedicated enough to making this happen that we've made sure 122 | to: 123 | 124 | - Use the same source code organization (as much as possible) as 125 | `DOM Testing Library` and `React Testing Library` 126 | - Use the same deployment process as `DOM Testing Library` 127 | - Use similar documentation styles 128 | 129 | And more! 130 | -------------------------------------------------------------------------------- /docs/fire-event.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Firing Events" 3 | --- 4 | 5 | > **Note** 6 | > 7 | > Most projects have a few use cases for `fireEvent`, but the majority of the 8 | > time you should probably use [`userEvent`](./user-event). 9 | 10 | ## `fireEvent` 11 | 12 | ```typescript 13 | fireEvent(instance: TestInstance, event: EventString, eventProperties?: Object) 14 | ``` 15 | 16 | Fire CLI events. 17 | 18 | ```javascript 19 | fireEvent(getByText(instance, 'Username:'), 'write', {value: 'crutchcorn'}) 20 | ``` 21 | 22 | ## `fireEvent[eventName]` 23 | 24 | ```typescript 25 | fireEvent[eventName](instance: TestInstance, eventProperties?: Object) 26 | ``` 27 | 28 | Convenience methods for firing CLI events. Check out 29 | [src/event-map.js](../src/event-map.ts) for a full list as well as default 30 | `eventProperties`. 31 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction" 3 | --- 4 | 5 | [CLI Testing Library](https://github.com/crutchcorn/cli-testing-library) 6 | implements as-close-as-possible API to 7 | [DOM Testing Library](https://github.com/testing-library/dom-testing-library) 8 | and 9 | [React Testing Library](https://github.com/testing-library/react-testing-library), 10 | but for E2E CLI tests instead. 11 | 12 | ``` 13 | npm install --save-dev cli-testing-library 14 | ``` 15 | 16 | - [`cli-testing-library` on GitHub](https://github.com/crutchcorn/cli-testing-library) 17 | 18 | # The problem 19 | 20 | You want to write maintainable tests for your CLI applications. As a part of 21 | this goal, you want your tests to avoid including implementation details of your 22 | CLI and rather focus on making your tests give you the confidence for which they 23 | are intended. As part of this, you want your testbase to be maintainable in the 24 | long run so refactors of your CLI (changes to implementation but not 25 | functionality) don't break your tests and slow you and your team down. 26 | 27 | # This solution 28 | 29 | The `CLI Testing Library` is a very light-weight solution for testing CLI 30 | applications (whether written in NodeJS or not). The main utilities it provides 31 | involve querying `stdout` in a way that's similar to how a user interacts with 32 | CLI. In this way, the library helps ensure your tests give you confidence in 33 | your UI code. The `CLI Testing Library`'s primary guiding principle is: 34 | 35 | > [The more your tests resemble the way your software is used, the more confidence they can give you.](https://testing-library.com/docs/guiding-principles/) 36 | 37 | As part of this goal, the utilities this library provides facilitate querying 38 | the CLI in the same way the user would. Finding text output with readable text, 39 | as opposed to ansi escapes interrupting (just like a user would focus on), 40 | sending keyboard events (like a user would), and more. 41 | 42 | This library encourages your applications to be more robust with user input and 43 | allows you to get your tests closer to using your command line the way a user 44 | will, which allows your tests to give you more confidence that your application 45 | will work when a real user uses it. 46 | 47 | **What this library is not**: 48 | 49 | 1. A test runner or framework 50 | 2. Specific to a testing framework 51 | 52 | # Further Reading 53 | 54 | - [API reference for `render` and friends](./api) 55 | - [Jest and Vitest matchers](./matchers) 56 | - [Mock user input](./user-event) 57 | - [Manually fire input events](./fire-event) 58 | - [Output matching queries](./queries) 59 | - [Debugging "CLI Testing Library" tests](./debug) 60 | - [Configure library options](./configure) 61 | - [Differences between us and other "Testing Library" projects](./differences) 62 | -------------------------------------------------------------------------------- /docs/matchers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Matchers" 3 | --- 4 | 5 | The `cli-testing-library` provides a set of custom Jest and Vitest matchers that you can 6 | use to extend Jest or Vitest. These will make your tests more declarative, clear to read 7 | and to maintain. 8 | 9 | ## Usage 10 | 11 | Import `cli-testing-library/jest`, `cli-testing-library/jest-globals`, or `cli-testing-library/vitest` once, based on your testing (for instance in your 12 | [tests setup file](https://jestjs.io/docs/en/configuration.html#setupfilesafterenv-array)) 13 | and you're good to go: 14 | 15 | ```javascript 16 | // In your own jest-setup.js (or any other name) 17 | import 'cli-testing-library/jest' 18 | 19 | // In jest.config.js add (if you haven't already) 20 | setupFilesAfterEnv: ['/jest-setup.js'] 21 | ``` 22 | 23 | ### With TypeScript 24 | 25 | If you're using TypeScript, make sure your setup file is a `.ts` and not a `.js` 26 | to include the necessary types. 27 | 28 | You will also need to include your setup file in your `tsconfig.json` if you 29 | haven't already: 30 | 31 | ```json 32 | // In tsconfig.json 33 | "include": [ 34 | ... 35 | "./jest-setup.ts" 36 | ], 37 | ``` 38 | 39 | ## Custom matchers 40 | 41 | ### `toBeInTheConsole` 42 | 43 | ```typescript 44 | toBeInTheConsole() 45 | ``` 46 | 47 | This allows you to assert whether an instance is present or not. Useful when 48 | combined with queries (such as `getByText` or `getByError`) that return the 49 | `TestInstance` 50 | 51 | #### Examples 52 | 53 | ```html 54 | Input your name: 55 | ``` 56 | 57 | ```javascript 58 | expect(getByText(instance, 'Input your name:')).toBeInTheConsole() 59 | ``` 60 | 61 |
62 | 63 | ### `toHaveErrorMessage` 64 | 65 | ```typescript 66 | toHaveErrorMessage(text: string | RegExp) 67 | ``` 68 | 69 | This allows you to check whether the given instance has an `stderr` message or 70 | not. 71 | 72 | Whitespace is normalized. 73 | 74 | When a `string` argument is passed through, it will perform a whole 75 | case-sensitive match to the error message text. 76 | 77 | To perform a case-insensitive match, you can use a `RegExp` with the `/i` 78 | modifier. 79 | 80 | To perform a partial match, you can pass a `RegExp`. 81 | 82 | #### Examples 83 | 84 | ```html 85 | File not found in output 86 | ``` 87 | 88 | ```javascript 89 | expect(instance).toHaveErrorMessage('File not found in output') 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/reference/classes/mutationobserver.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: MutationObserver 3 | title: MutationObserver 4 | --- 5 | 6 | 7 | 8 | # Class: MutationObserver 9 | 10 | Defined in: [mutation-observer.ts:9](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L9) 11 | 12 | ## Constructors 13 | 14 | ### new MutationObserver() 15 | 16 | ```ts 17 | new MutationObserver(cb): MutationObserver 18 | ``` 19 | 20 | Defined in: [mutation-observer.ts:13](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L13) 21 | 22 | #### Parameters 23 | 24 | ##### cb 25 | 26 | () => `void` 27 | 28 | #### Returns 29 | 30 | [`MutationObserver`](mutationobserver.md) 31 | 32 | ## Properties 33 | 34 | ### \_cb() 35 | 36 | ```ts 37 | _cb: () => void; 38 | ``` 39 | 40 | Defined in: [mutation-observer.ts:10](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L10) 41 | 42 | #### Returns 43 | 44 | `void` 45 | 46 | *** 47 | 48 | ### \_id 49 | 50 | ```ts 51 | _id: number; 52 | ``` 53 | 54 | Defined in: [mutation-observer.ts:11](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L11) 55 | 56 | ## Methods 57 | 58 | ### disconnect() 59 | 60 | ```ts 61 | disconnect(): void 62 | ``` 63 | 64 | Defined in: [mutation-observer.ts:22](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L22) 65 | 66 | #### Returns 67 | 68 | `void` 69 | 70 | *** 71 | 72 | ### observe() 73 | 74 | ```ts 75 | observe(): void 76 | ``` 77 | 78 | Defined in: [mutation-observer.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L18) 79 | 80 | #### Returns 81 | 82 | `void` 83 | -------------------------------------------------------------------------------- /docs/reference/functions/bindobjectfnstoinstance.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: bindObjectFnsToInstance 3 | title: bindObjectFnsToInstance 4 | --- 5 | 6 | 7 | 8 | # Function: bindObjectFnsToInstance() 9 | 10 | ```ts 11 | function bindObjectFnsToInstance(instance, object): Record unknown> 12 | ``` 13 | 14 | Defined in: [helpers.ts:75](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/helpers.ts#L75) 15 | 16 | This is used to bind a series of functions where `instance` is the first argument 17 | to an instance, removing the implicit first argument. 18 | 19 | ## Parameters 20 | 21 | ### instance 22 | 23 | `TestInstance` 24 | 25 | ### object 26 | 27 | `Record`\<`string`, (...`props`) => `unknown`\> 28 | 29 | ## Returns 30 | 31 | `Record`\<`string`, (...`props`) => `unknown`\> 32 | -------------------------------------------------------------------------------- /docs/reference/functions/buildqueries.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: buildQueries 3 | title: buildQueries 4 | --- 5 | 6 | 7 | 8 | # Function: buildQueries() 9 | 10 | ```ts 11 | function buildQueries(queryBy, getMissingError): readonly [(container, ...args) => T, (container, ...args) => T, (instance, text, options?, waitForOptions?) => Promise] 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:115](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L115) 15 | 16 | ## Parameters 17 | 18 | ### queryBy 19 | 20 | [`QueryMethod`](../type-aliases/querymethod.md)\<\[[`Matcher`](../type-aliases/matcher.md), [`MatcherOptions`](../interfaces/matcheroptions.md)\], `null` \| `TestInstance`\> 21 | 22 | ### getMissingError 23 | 24 | [`GetErrorFunction`](../type-aliases/geterrorfunction.md)\<\[[`Matcher`](../type-aliases/matcher.md), [`MatcherOptions`](../interfaces/matcheroptions.md)\]\> 25 | 26 | ## Returns 27 | 28 | readonly \[\<`T`\>(`container`, ...`args`) => `T`, \<`T`\>(`container`, ...`args`) => `T`, \<`T`\>(`instance`, `text`, `options`?, `waitForOptions`?) => `Promise`\<`T`\>\] 29 | -------------------------------------------------------------------------------- /docs/reference/functions/cleanup.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: cleanup 3 | title: cleanup 4 | --- 5 | 6 | 7 | 8 | # Function: cleanup() 9 | 10 | ```ts 11 | function cleanup(): Promise 12 | ``` 13 | 14 | Defined in: [pure.ts:167](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L167) 15 | 16 | ## Returns 17 | 18 | `Promise`\<`void`[]\> 19 | -------------------------------------------------------------------------------- /docs/reference/functions/configure.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: configure 3 | title: configure 4 | --- 5 | 6 | 7 | 8 | # Function: configure() 9 | 10 | ```ts 11 | function configure(newConfig): void 12 | ``` 13 | 14 | Defined in: [config.ts:77](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L77) 15 | 16 | ## Parameters 17 | 18 | ### newConfig 19 | 20 | [`ConfigFn`](../interfaces/configfn.md) | `Partial`\<[`Config`](../interfaces/config.md)\> 21 | 22 | ## Returns 23 | 24 | `void` 25 | -------------------------------------------------------------------------------- /docs/reference/functions/debounce.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: debounce 3 | title: debounce 4 | --- 5 | 6 | 7 | 8 | # Function: debounce() 9 | 10 | ```ts 11 | function debounce(func, timeout): (...args) => void 12 | ``` 13 | 14 | Defined in: [helpers.ts:56](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/helpers.ts#L56) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* (...`args`) => `void` 19 | 20 | ## Parameters 21 | 22 | ### func 23 | 24 | `T` 25 | 26 | ### timeout 27 | 28 | `number` 29 | 30 | ## Returns 31 | 32 | `Function` 33 | 34 | ### Parameters 35 | 36 | #### args 37 | 38 | ...`Parameters`\<`T`\> 39 | 40 | ### Returns 41 | 42 | `void` 43 | -------------------------------------------------------------------------------- /docs/reference/functions/fireevent.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: fireEvent 3 | title: fireEvent 4 | --- 5 | 6 | 7 | 8 | # Function: fireEvent() 9 | 10 | ```ts 11 | function fireEvent( 12 | instance, 13 | event, 14 | options?): boolean | Promise 15 | ``` 16 | 17 | Defined in: [events.ts:20](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/events.ts#L20) 18 | 19 | ## Type Parameters 20 | 21 | • **TEventType** *extends* `"sigterm"` \| `"sigkill"` \| `"write"` 22 | 23 | ## Parameters 24 | 25 | ### instance 26 | 27 | `TestInstance` 28 | 29 | ### event 30 | 31 | `TEventType` 32 | 33 | ### options? 34 | 35 | `Parameters`\<`object`\[`TEventType`\]\>\[`1`\] 36 | 37 | ## Returns 38 | 39 | `boolean` \| `Promise`\<`void`\> 40 | -------------------------------------------------------------------------------- /docs/reference/functions/getconfig.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getConfig 3 | title: getConfig 4 | --- 5 | 6 | 7 | 8 | # Function: getConfig() 9 | 10 | ```ts 11 | function getConfig(): Config 12 | ``` 13 | 14 | Defined in: [config.ts:91](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L91) 15 | 16 | ## Returns 17 | 18 | [`Config`](../interfaces/config.md) 19 | -------------------------------------------------------------------------------- /docs/reference/functions/getcurrentinstance.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getCurrentInstance 3 | title: getCurrentInstance 4 | --- 5 | 6 | 7 | 8 | # Function: getCurrentInstance() 9 | 10 | ```ts 11 | function getCurrentInstance(): undefined | TestInstance 12 | ``` 13 | 14 | Defined in: [helpers.ts:33](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/helpers.ts#L33) 15 | 16 | ## Returns 17 | 18 | `undefined` \| `TestInstance` 19 | -------------------------------------------------------------------------------- /docs/reference/functions/getdefaultnormalizer.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getDefaultNormalizer 3 | title: getDefaultNormalizer 4 | --- 5 | 6 | 7 | 8 | # Function: getDefaultNormalizer() 9 | 10 | ```ts 11 | function getDefaultNormalizer(__namedParameters): NormalizerFn 12 | ``` 13 | 14 | Defined in: [matches.ts:104](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L104) 15 | 16 | ## Parameters 17 | 18 | ### \_\_namedParameters 19 | 20 | [`DefaultNormalizerOptions`](../interfaces/defaultnormalizeroptions.md) = `{}` 21 | 22 | ## Returns 23 | 24 | [`NormalizerFn`](../type-aliases/normalizerfn.md) 25 | -------------------------------------------------------------------------------- /docs/reference/functions/getinstanceerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getInstanceError 3 | title: getInstanceError 4 | --- 5 | 6 | 7 | 8 | # Function: getInstanceError() 9 | 10 | ```ts 11 | function getInstanceError(message, instance): Error 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:26](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L26) 15 | 16 | ## Parameters 17 | 18 | ### message 19 | 20 | `null` | `string` 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ## Returns 27 | 28 | `Error` 29 | -------------------------------------------------------------------------------- /docs/reference/functions/getqueriesforelement.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getQueriesForElement 3 | title: getQueriesForElement 4 | --- 5 | 6 | 7 | 8 | # Function: getQueriesForElement() 9 | 10 | ```ts 11 | function getQueriesForElement( 12 | instance, 13 | queries, 14 | initialValue): BoundFunctions 15 | ``` 16 | 17 | Defined in: [get-queries-for-instance.ts:50](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/get-queries-for-instance.ts#L50) 18 | 19 | ## Type Parameters 20 | 21 | • **T** *extends* [`Queries`](../interfaces/queries.md) = [`queries`](../namespaces/queries/index.md) 22 | 23 | ## Parameters 24 | 25 | ### instance 26 | 27 | `TestInstance` 28 | 29 | ### queries 30 | 31 | `T` = `...` 32 | 33 | object of functions 34 | 35 | ### initialValue 36 | 37 | for reducer 38 | 39 | ## Returns 40 | 41 | [`BoundFunctions`](../type-aliases/boundfunctions.md)\<`T`\> 42 | 43 | returns object of functions bound to container 44 | -------------------------------------------------------------------------------- /docs/reference/functions/jestfaketimersareenabled.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: jestFakeTimersAreEnabled 3 | title: jestFakeTimersAreEnabled 4 | --- 5 | 6 | 7 | 8 | # Function: jestFakeTimersAreEnabled() 9 | 10 | ```ts 11 | function jestFakeTimersAreEnabled(): boolean 12 | ``` 13 | 14 | Defined in: [helpers.ts:3](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/helpers.ts#L3) 15 | 16 | ## Returns 17 | 18 | `boolean` 19 | -------------------------------------------------------------------------------- /docs/reference/functions/makefindquery.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: makeFindQuery 3 | title: makeFindQuery 4 | --- 5 | 6 | 7 | 8 | # Function: makeFindQuery() 9 | 10 | ```ts 11 | function makeFindQuery(getter): (instance, text, options?, waitForOptions?) => Promise 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:66](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L66) 15 | 16 | ## Type Parameters 17 | 18 | • **TQueryFor** 19 | 20 | ## Parameters 21 | 22 | ### getter 23 | 24 | (`container`, `text`, `options`?) => `TQueryFor` 25 | 26 | ## Returns 27 | 28 | `Function` 29 | 30 | ### Type Parameters 31 | 32 | • **T** *extends* `TestInstance` = `TestInstance` 33 | 34 | ### Parameters 35 | 36 | #### instance 37 | 38 | `TestInstance` 39 | 40 | #### text 41 | 42 | [`Matcher`](../type-aliases/matcher.md) 43 | 44 | #### options? 45 | 46 | [`MatcherOptions`](../interfaces/matcheroptions.md) 47 | 48 | #### waitForOptions? 49 | 50 | [`waitForOptions`](../interfaces/waitforoptions.md) 51 | 52 | ### Returns 53 | 54 | `Promise`\<`T`\> 55 | -------------------------------------------------------------------------------- /docs/reference/functions/makenormalizer.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: makeNormalizer 3 | title: makeNormalizer 4 | --- 5 | 6 | 7 | 8 | # Function: makeNormalizer() 9 | 10 | ```ts 11 | function makeNormalizer(props): NormalizerFn 12 | ``` 13 | 14 | Defined in: [matches.ts:132](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L132) 15 | 16 | ## Parameters 17 | 18 | ### props 19 | 20 | [`NormalizerOptions`](../interfaces/normalizeroptions.md) 21 | 22 | Constructs a normalizer to pass to functions in matches.js 23 | 24 | ## Returns 25 | 26 | [`NormalizerFn`](../type-aliases/normalizerfn.md) 27 | 28 | A normalizer 29 | -------------------------------------------------------------------------------- /docs/reference/functions/render.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: render 3 | title: render 4 | --- 5 | 6 | 7 | 8 | # Function: render() 9 | 10 | ```ts 11 | function render( 12 | command, 13 | args, 14 | opts): Promise 15 | ``` 16 | 17 | Defined in: [pure.ts:40](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L40) 18 | 19 | ## Parameters 20 | 21 | ### command 22 | 23 | `string` 24 | 25 | ### args 26 | 27 | `string`[] = `[]` 28 | 29 | ### opts 30 | 31 | `Partial`\<[`RenderOptions`](../interfaces/renderoptions.md)\> = `{}` 32 | 33 | ## Returns 34 | 35 | `Promise`\<[`RenderResult`](../type-aliases/renderresult.md)\> 36 | -------------------------------------------------------------------------------- /docs/reference/functions/runobservers.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: _runObservers 3 | title: _runObservers 4 | --- 5 | 6 | 7 | 8 | # Function: \_runObservers() 9 | 10 | ```ts 11 | function _runObservers(): void 12 | ``` 13 | 14 | Defined in: [mutation-observer.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/mutation-observer.ts#L27) 15 | 16 | ## Returns 17 | 18 | `void` 19 | -------------------------------------------------------------------------------- /docs/reference/functions/runwithexpensiveerrordiagnosticsdisabled.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: runWithExpensiveErrorDiagnosticsDisabled 3 | title: runWithExpensiveErrorDiagnosticsDisabled 4 | --- 5 | 6 | 7 | 8 | # Function: runWithExpensiveErrorDiagnosticsDisabled() 9 | 10 | ```ts 11 | function runWithExpensiveErrorDiagnosticsDisabled(callback): T 12 | ``` 13 | 14 | Defined in: [config.ts:66](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L66) 15 | 16 | ## Type Parameters 17 | 18 | • **T** 19 | 20 | ## Parameters 21 | 22 | ### callback 23 | 24 | `Callback`\<`T`\> 25 | 26 | ## Returns 27 | 28 | `T` 29 | -------------------------------------------------------------------------------- /docs/reference/functions/setcurrentinstance.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: setCurrentInstance 3 | title: setCurrentInstance 4 | --- 5 | 6 | 7 | 8 | # Function: setCurrentInstance() 9 | 10 | ```ts 11 | function setCurrentInstance(newInstance): void 12 | ``` 13 | 14 | Defined in: [helpers.ts:52](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/helpers.ts#L52) 15 | 16 | ## Parameters 17 | 18 | ### newInstance 19 | 20 | `TestInstance` 21 | 22 | ## Returns 23 | 24 | `void` 25 | -------------------------------------------------------------------------------- /docs/reference/functions/waitfor.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: waitFor 3 | title: waitFor 4 | --- 5 | 6 | 7 | 8 | # Function: waitFor() 9 | 10 | ```ts 11 | function waitFor(callback, options?): Promise 12 | ``` 13 | 14 | Defined in: [wait-for.ts:196](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L196) 15 | 16 | ## Type Parameters 17 | 18 | • **T** 19 | 20 | ## Parameters 21 | 22 | ### callback 23 | 24 | () => `T` \| `Promise`\<`T`\> 25 | 26 | ### options? 27 | 28 | [`waitForOptions`](../interfaces/waitforoptions.md) 29 | 30 | ## Returns 31 | 32 | `Promise`\<`T`\> 33 | -------------------------------------------------------------------------------- /docs/reference/functions/wrapsinglequerywithsuggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: wrapSingleQueryWithSuggestion 3 | title: wrapSingleQueryWithSuggestion 4 | --- 5 | 6 | 7 | 8 | # Function: wrapSingleQueryWithSuggestion() 9 | 10 | ```ts 11 | function wrapSingleQueryWithSuggestion( 12 | query, 13 | queryByName, 14 | variant): (container, ...args) => T 15 | ``` 16 | 17 | Defined in: [query-helpers.ts:89](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L89) 18 | 19 | ## Type Parameters 20 | 21 | • **TArguments** *extends* `unknown`[] 22 | 23 | ## Parameters 24 | 25 | ### query 26 | 27 | (`container`, ...`args`) => `null` \| `TestInstance` 28 | 29 | ### queryByName 30 | 31 | `string` 32 | 33 | ### variant 34 | 35 | `Variant` 36 | 37 | ## Returns 38 | 39 | `Function` 40 | 41 | ### Type Parameters 42 | 43 | • **T** *extends* `TestInstance` = `TestInstance` 44 | 45 | ### Parameters 46 | 47 | #### container 48 | 49 | `TestInstance` 50 | 51 | #### args 52 | 53 | ...`TArguments` 54 | 55 | ### Returns 56 | 57 | `T` 58 | -------------------------------------------------------------------------------- /docs/reference/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: cli-testing-library 3 | title: cli-testing-library 4 | --- 5 | 6 | 7 | 8 | # cli-testing-library 9 | 10 | ## References 11 | 12 | ### findByError 13 | 14 | Re-exports [findByError](namespaces/queries/functions/findbyerror.md) 15 | 16 | *** 17 | 18 | ### FindByError 19 | 20 | Re-exports [FindByError](namespaces/queries/type-aliases/findbyerror.md) 21 | 22 | *** 23 | 24 | ### findByText 25 | 26 | Re-exports [findByText](namespaces/queries/functions/findbytext.md) 27 | 28 | *** 29 | 30 | ### FindByText 31 | 32 | Re-exports [FindByText](namespaces/queries/type-aliases/findbytext.md) 33 | 34 | *** 35 | 36 | ### getByError 37 | 38 | Re-exports [getByError](namespaces/queries/functions/getbyerror.md) 39 | 40 | *** 41 | 42 | ### GetByError 43 | 44 | Re-exports [GetByError](namespaces/queries/type-aliases/getbyerror.md) 45 | 46 | *** 47 | 48 | ### getByText 49 | 50 | Re-exports [getByText](namespaces/queries/functions/getbytext.md) 51 | 52 | *** 53 | 54 | ### GetByText 55 | 56 | Re-exports [GetByText](namespaces/queries/type-aliases/getbytext.md) 57 | 58 | *** 59 | 60 | ### queryByError 61 | 62 | Re-exports [queryByError](namespaces/queries/functions/querybyerror.md) 63 | 64 | *** 65 | 66 | ### QueryByError 67 | 68 | Re-exports [QueryByError](namespaces/queries/type-aliases/querybyerror.md) 69 | 70 | *** 71 | 72 | ### queryByText 73 | 74 | Re-exports [queryByText](namespaces/queries/functions/querybytext.md) 75 | 76 | *** 77 | 78 | ### QueryByText 79 | 80 | Re-exports [QueryByText](namespaces/queries/type-aliases/querybytext.md) 81 | 82 | ## Namespaces 83 | 84 | - [queries](namespaces/queries/index.md) 85 | 86 | ## Classes 87 | 88 | - [MutationObserver](classes/mutationobserver.md) 89 | 90 | ## Interfaces 91 | 92 | - [Config](interfaces/config.md) 93 | - [ConfigFn](interfaces/configfn.md) 94 | - [DefaultNormalizerOptions](interfaces/defaultnormalizeroptions.md) 95 | - [keyboardKey](interfaces/keyboardkey.md) 96 | - [MatcherOptions](interfaces/matcheroptions.md) 97 | - [NormalizerOptions](interfaces/normalizeroptions.md) 98 | - [Queries](interfaces/queries.md) 99 | - [RenderOptions](interfaces/renderoptions.md) 100 | - [SelectorMatcherOptions](interfaces/selectormatcheroptions.md) 101 | - [waitForOptions](interfaces/waitforoptions.md) 102 | 103 | ## Type Aliases 104 | 105 | - [BoundFunction](type-aliases/boundfunction.md) 106 | - [BoundFunctions](type-aliases/boundfunctions.md) 107 | - [EventType](type-aliases/eventtype.md) 108 | - [FireFunction](type-aliases/firefunction.md) 109 | - [FireObject](type-aliases/fireobject.md) 110 | - [GetErrorFunction](type-aliases/geterrorfunction.md) 111 | - [Match](type-aliases/match.md) 112 | - [Matcher](type-aliases/matcher.md) 113 | - [MatcherFunction](type-aliases/matcherfunction.md) 114 | - [NormalizerFn](type-aliases/normalizerfn.md) 115 | - [Query](type-aliases/query.md) 116 | - [QueryMethod](type-aliases/querymethod.md) 117 | - [RenderResult](type-aliases/renderresult.md) 118 | - [WithSuggest](type-aliases/withsuggest.md) 119 | 120 | ## Functions 121 | 122 | - [\_runObservers](functions/runobservers.md) 123 | - [bindObjectFnsToInstance](functions/bindobjectfnstoinstance.md) 124 | - [buildQueries](functions/buildqueries.md) 125 | - [cleanup](functions/cleanup.md) 126 | - [configure](functions/configure.md) 127 | - [debounce](functions/debounce.md) 128 | - [fireEvent](functions/fireevent.md) 129 | - [getConfig](functions/getconfig.md) 130 | - [getCurrentInstance](functions/getcurrentinstance.md) 131 | - [getDefaultNormalizer](functions/getdefaultnormalizer.md) 132 | - [getInstanceError](functions/getinstanceerror.md) 133 | - [getQueriesForElement](functions/getqueriesforelement.md) 134 | - [jestFakeTimersAreEnabled](functions/jestfaketimersareenabled.md) 135 | - [makeFindQuery](functions/makefindquery.md) 136 | - [makeNormalizer](functions/makenormalizer.md) 137 | - [render](functions/render.md) 138 | - [runWithExpensiveErrorDiagnosticsDisabled](functions/runwithexpensiveerrordiagnosticsdisabled.md) 139 | - [setCurrentInstance](functions/setcurrentinstance.md) 140 | - [waitFor](functions/waitfor.md) 141 | - [wrapSingleQueryWithSuggestion](functions/wrapsinglequerywithsuggestion.md) 142 | -------------------------------------------------------------------------------- /docs/reference/interfaces/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: Config 3 | title: Config 4 | --- 5 | 6 | 7 | 8 | # Interface: Config 9 | 10 | Defined in: [config.ts:5](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L5) 11 | 12 | ## Properties 13 | 14 | ### asyncUtilTimeout 15 | 16 | ```ts 17 | asyncUtilTimeout: number; 18 | ``` 19 | 20 | Defined in: [config.ts:13](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L13) 21 | 22 | *** 23 | 24 | ### errorDebounceTimeout 25 | 26 | ```ts 27 | errorDebounceTimeout: number; 28 | ``` 29 | 30 | Defined in: [config.ts:15](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L15) 31 | 32 | *** 33 | 34 | ### getInstanceError() 35 | 36 | ```ts 37 | getInstanceError: (message, container) => Error; 38 | ``` 39 | 40 | Defined in: [config.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L18) 41 | 42 | #### Parameters 43 | 44 | ##### message 45 | 46 | `null` | `string` 47 | 48 | ##### container 49 | 50 | `TestInstance` 51 | 52 | #### Returns 53 | 54 | `Error` 55 | 56 | *** 57 | 58 | ### renderAwaitTime 59 | 60 | ```ts 61 | renderAwaitTime: number; 62 | ``` 63 | 64 | Defined in: [config.ts:14](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L14) 65 | 66 | *** 67 | 68 | ### showOriginalStackTrace 69 | 70 | ```ts 71 | showOriginalStackTrace: boolean; 72 | ``` 73 | 74 | Defined in: [config.ts:16](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L16) 75 | 76 | *** 77 | 78 | ### throwSuggestions 79 | 80 | ```ts 81 | throwSuggestions: boolean; 82 | ``` 83 | 84 | Defined in: [config.ts:17](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L17) 85 | 86 | *** 87 | 88 | ### unstable\_advanceTimersWrapper() 89 | 90 | ```ts 91 | unstable_advanceTimersWrapper: (cb) => unknown; 92 | ``` 93 | 94 | Defined in: [config.ts:10](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L10) 95 | 96 | WARNING: `unstable` prefix means this API may change in patch and minor releases. 97 | 98 | #### Parameters 99 | 100 | ##### cb 101 | 102 | (...`args`) => `unknown` 103 | 104 | #### Returns 105 | 106 | `unknown` 107 | -------------------------------------------------------------------------------- /docs/reference/interfaces/configfn.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: ConfigFn 3 | title: ConfigFn 4 | --- 5 | 6 | 7 | 8 | # Interface: ConfigFn() 9 | 10 | Defined in: [config.ts:21](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L21) 11 | 12 | ```ts 13 | interface ConfigFn(existingConfig): Partial 14 | ``` 15 | 16 | Defined in: [config.ts:22](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/config.ts#L22) 17 | 18 | ## Parameters 19 | 20 | ### existingConfig 21 | 22 | [`Config`](config.md) 23 | 24 | ## Returns 25 | 26 | `Partial`\<[`Config`](config.md)\> 27 | -------------------------------------------------------------------------------- /docs/reference/interfaces/defaultnormalizeroptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: DefaultNormalizerOptions 3 | title: DefaultNormalizerOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: DefaultNormalizerOptions 9 | 10 | Defined in: [matches.ts:37](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L37) 11 | 12 | ## Extended by 13 | 14 | - [`NormalizerOptions`](normalizeroptions.md) 15 | 16 | ## Properties 17 | 18 | ### collapseWhitespace? 19 | 20 | ```ts 21 | optional collapseWhitespace: boolean; 22 | ``` 23 | 24 | Defined in: [matches.ts:39](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L39) 25 | 26 | *** 27 | 28 | ### stripAnsi? 29 | 30 | ```ts 31 | optional stripAnsi: boolean; 32 | ``` 33 | 34 | Defined in: [matches.ts:40](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L40) 35 | 36 | *** 37 | 38 | ### trim? 39 | 40 | ```ts 41 | optional trim: boolean; 42 | ``` 43 | 44 | Defined in: [matches.ts:38](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L38) 45 | -------------------------------------------------------------------------------- /docs/reference/interfaces/keyboardkey.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: keyboardKey 3 | title: keyboardKey 4 | --- 5 | 6 | 7 | 8 | # Interface: keyboardKey 9 | 10 | Defined in: [user-event/keyboard/types.ts:8](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/user-event/keyboard/types.ts#L8) 11 | 12 | ## Properties 13 | 14 | ### code? 15 | 16 | ```ts 17 | optional code: string; 18 | ``` 19 | 20 | Defined in: [user-event/keyboard/types.ts:10](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/user-event/keyboard/types.ts#L10) 21 | 22 | Physical location on a keyboard 23 | 24 | *** 25 | 26 | ### hex? 27 | 28 | ```ts 29 | optional hex: string; 30 | ``` 31 | 32 | Defined in: [user-event/keyboard/types.ts:12](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/user-event/keyboard/types.ts#L12) 33 | 34 | Character or functional key hex code 35 | -------------------------------------------------------------------------------- /docs/reference/interfaces/matcheroptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: MatcherOptions 3 | title: MatcherOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: MatcherOptions 9 | 10 | Defined in: [matches.ts:17](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L17) 11 | 12 | ## Extended by 13 | 14 | - [`SelectorMatcherOptions`](selectormatcheroptions.md) 15 | 16 | ## Properties 17 | 18 | ### collapseWhitespace? 19 | 20 | ```ts 21 | optional collapseWhitespace: boolean; 22 | ``` 23 | 24 | Defined in: [matches.ts:24](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L24) 25 | 26 | Use normalizer with getDefaultNormalizer instead 27 | 28 | *** 29 | 30 | ### exact? 31 | 32 | ```ts 33 | optional exact: boolean; 34 | ``` 35 | 36 | Defined in: [matches.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L18) 37 | 38 | *** 39 | 40 | ### normalizer? 41 | 42 | ```ts 43 | optional normalizer: NormalizerFn; 44 | ``` 45 | 46 | Defined in: [matches.ts:25](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L25) 47 | 48 | *** 49 | 50 | ### stripAnsi? 51 | 52 | ```ts 53 | optional stripAnsi: boolean; 54 | ``` 55 | 56 | Defined in: [matches.ts:22](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L22) 57 | 58 | Use normalizer with getDefaultNormalizer instead 59 | 60 | *** 61 | 62 | ### suggest? 63 | 64 | ```ts 65 | optional suggest: boolean; 66 | ``` 67 | 68 | Defined in: [matches.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L27) 69 | 70 | suppress suggestions for a specific query 71 | 72 | *** 73 | 74 | ### trim? 75 | 76 | ```ts 77 | optional trim: boolean; 78 | ``` 79 | 80 | Defined in: [matches.ts:20](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L20) 81 | 82 | Use normalizer with getDefaultNormalizer instead 83 | -------------------------------------------------------------------------------- /docs/reference/interfaces/normalizeroptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: NormalizerOptions 3 | title: NormalizerOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: NormalizerOptions 9 | 10 | Defined in: [matches.ts:13](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L13) 11 | 12 | ## Extends 13 | 14 | - [`DefaultNormalizerOptions`](defaultnormalizeroptions.md) 15 | 16 | ## Properties 17 | 18 | ### collapseWhitespace? 19 | 20 | ```ts 21 | optional collapseWhitespace: boolean; 22 | ``` 23 | 24 | Defined in: [matches.ts:39](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L39) 25 | 26 | #### Inherited from 27 | 28 | [`DefaultNormalizerOptions`](defaultnormalizeroptions.md).[`collapseWhitespace`](DefaultNormalizerOptions.md#collapsewhitespace) 29 | 30 | *** 31 | 32 | ### normalizer? 33 | 34 | ```ts 35 | optional normalizer: NormalizerFn; 36 | ``` 37 | 38 | Defined in: [matches.ts:14](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L14) 39 | 40 | *** 41 | 42 | ### stripAnsi? 43 | 44 | ```ts 45 | optional stripAnsi: boolean; 46 | ``` 47 | 48 | Defined in: [matches.ts:40](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L40) 49 | 50 | #### Inherited from 51 | 52 | [`DefaultNormalizerOptions`](defaultnormalizeroptions.md).[`stripAnsi`](DefaultNormalizerOptions.md#stripansi) 53 | 54 | *** 55 | 56 | ### trim? 57 | 58 | ```ts 59 | optional trim: boolean; 60 | ``` 61 | 62 | Defined in: [matches.ts:38](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L38) 63 | 64 | #### Inherited from 65 | 66 | [`DefaultNormalizerOptions`](defaultnormalizeroptions.md).[`trim`](DefaultNormalizerOptions.md#trim) 67 | -------------------------------------------------------------------------------- /docs/reference/interfaces/queries.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: Queries 3 | title: Queries 4 | --- 5 | 6 | 7 | 8 | # Interface: Queries 9 | 10 | Defined in: [get-queries-for-instance.ts:40](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/get-queries-for-instance.ts#L40) 11 | 12 | ## Indexable 13 | 14 | ```ts 15 | [T: string]: Query 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/reference/interfaces/renderoptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: RenderOptions 3 | title: RenderOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: RenderOptions 9 | 10 | Defined in: [pure.ts:24](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L24) 11 | 12 | ## Properties 13 | 14 | ### cwd 15 | 16 | ```ts 17 | cwd: string; 18 | ``` 19 | 20 | Defined in: [pure.ts:25](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L25) 21 | 22 | *** 23 | 24 | ### debug 25 | 26 | ```ts 27 | debug: boolean; 28 | ``` 29 | 30 | Defined in: [pure.ts:26](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L26) 31 | 32 | *** 33 | 34 | ### spawnOpts 35 | 36 | ```ts 37 | spawnOpts: Omit; 38 | ``` 39 | 40 | Defined in: [pure.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L27) 41 | -------------------------------------------------------------------------------- /docs/reference/interfaces/selectormatcheroptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: SelectorMatcherOptions 3 | title: SelectorMatcherOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: SelectorMatcherOptions 9 | 10 | Defined in: [query-helpers.ts:16](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L16) 11 | 12 | ## Extends 13 | 14 | - [`MatcherOptions`](matcheroptions.md) 15 | 16 | ## Properties 17 | 18 | ### collapseWhitespace? 19 | 20 | ```ts 21 | optional collapseWhitespace: boolean; 22 | ``` 23 | 24 | Defined in: [matches.ts:24](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L24) 25 | 26 | Use normalizer with getDefaultNormalizer instead 27 | 28 | #### Inherited from 29 | 30 | [`MatcherOptions`](matcheroptions.md).[`collapseWhitespace`](MatcherOptions.md#collapsewhitespace) 31 | 32 | *** 33 | 34 | ### exact? 35 | 36 | ```ts 37 | optional exact: boolean; 38 | ``` 39 | 40 | Defined in: [matches.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L18) 41 | 42 | #### Inherited from 43 | 44 | [`MatcherOptions`](matcheroptions.md).[`exact`](MatcherOptions.md#exact) 45 | 46 | *** 47 | 48 | ### ignore? 49 | 50 | ```ts 51 | optional ignore: string | boolean; 52 | ``` 53 | 54 | Defined in: [query-helpers.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L18) 55 | 56 | *** 57 | 58 | ### normalizer? 59 | 60 | ```ts 61 | optional normalizer: NormalizerFn; 62 | ``` 63 | 64 | Defined in: [matches.ts:25](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L25) 65 | 66 | #### Inherited from 67 | 68 | [`MatcherOptions`](matcheroptions.md).[`normalizer`](MatcherOptions.md#normalizer) 69 | 70 | *** 71 | 72 | ### selector? 73 | 74 | ```ts 75 | optional selector: string; 76 | ``` 77 | 78 | Defined in: [query-helpers.ts:17](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L17) 79 | 80 | *** 81 | 82 | ### stripAnsi? 83 | 84 | ```ts 85 | optional stripAnsi: boolean; 86 | ``` 87 | 88 | Defined in: [matches.ts:22](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L22) 89 | 90 | Use normalizer with getDefaultNormalizer instead 91 | 92 | #### Inherited from 93 | 94 | [`MatcherOptions`](matcheroptions.md).[`stripAnsi`](MatcherOptions.md#stripansi) 95 | 96 | *** 97 | 98 | ### suggest? 99 | 100 | ```ts 101 | optional suggest: boolean; 102 | ``` 103 | 104 | Defined in: [matches.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L27) 105 | 106 | suppress suggestions for a specific query 107 | 108 | #### Inherited from 109 | 110 | [`MatcherOptions`](matcheroptions.md).[`suggest`](MatcherOptions.md#suggest) 111 | 112 | *** 113 | 114 | ### trim? 115 | 116 | ```ts 117 | optional trim: boolean; 118 | ``` 119 | 120 | Defined in: [matches.ts:20](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L20) 121 | 122 | Use normalizer with getDefaultNormalizer instead 123 | 124 | #### Inherited from 125 | 126 | [`MatcherOptions`](matcheroptions.md).[`trim`](MatcherOptions.md#trim) 127 | -------------------------------------------------------------------------------- /docs/reference/interfaces/waitforoptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: waitForOptions 3 | title: waitForOptions 4 | --- 5 | 6 | 7 | 8 | # Interface: waitForOptions 9 | 10 | Defined in: [wait-for.ts:14](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L14) 11 | 12 | ## Properties 13 | 14 | ### instance? 15 | 16 | ```ts 17 | optional instance: TestInstance; 18 | ``` 19 | 20 | Defined in: [wait-for.ts:15](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L15) 21 | 22 | *** 23 | 24 | ### interval? 25 | 26 | ```ts 27 | optional interval: number; 28 | ``` 29 | 30 | Defined in: [wait-for.ts:18](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L18) 31 | 32 | *** 33 | 34 | ### onTimeout()? 35 | 36 | ```ts 37 | optional onTimeout: (error) => Error; 38 | ``` 39 | 40 | Defined in: [wait-for.ts:19](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L19) 41 | 42 | #### Parameters 43 | 44 | ##### error 45 | 46 | `Error` 47 | 48 | #### Returns 49 | 50 | `Error` 51 | 52 | *** 53 | 54 | ### showOriginalStackTrace? 55 | 56 | ```ts 57 | optional showOriginalStackTrace: boolean; 58 | ``` 59 | 60 | Defined in: [wait-for.ts:16](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L16) 61 | 62 | *** 63 | 64 | ### stackTraceError? 65 | 66 | ```ts 67 | optional stackTraceError: Error; 68 | ``` 69 | 70 | Defined in: [wait-for.ts:20](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L20) 71 | 72 | *** 73 | 74 | ### timeout? 75 | 76 | ```ts 77 | optional timeout: number; 78 | ``` 79 | 80 | Defined in: [wait-for.ts:17](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/wait-for.ts#L17) 81 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/findbyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: findByError 3 | title: findByError 4 | --- 5 | 6 | 7 | 8 | # Function: findByError() 9 | 10 | ```ts 11 | function findByError(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/error.ts:71](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L71) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md), [`waitForOptions`](../../../interfaces/waitforoptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`FindByError`](../type-aliases/findbyerror.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/findbytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: findByText 3 | title: findByText 4 | --- 5 | 6 | 7 | 8 | # Function: findByText() 9 | 10 | ```ts 11 | function findByText(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/text.ts:69](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L69) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md), [`waitForOptions`](../../../interfaces/waitforoptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`FindByText`](../type-aliases/findbytext.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/getbyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getByError 3 | title: getByError 4 | --- 5 | 6 | 7 | 8 | # Function: getByError() 9 | 10 | ```ts 11 | function getByError(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/error.ts:59](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L59) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`GetByError`](../type-aliases/getbyerror.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/getbytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getByText 3 | title: getByText 4 | --- 5 | 6 | 7 | 8 | # Function: getByText() 9 | 10 | ```ts 11 | function getByText(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/text.ts:59](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L59) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`GetByText`](../type-aliases/getbytext.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/querybyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: queryByError 3 | title: queryByError 4 | --- 5 | 6 | 7 | 8 | # Function: queryByError() 9 | 10 | ```ts 11 | function queryByError(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/error.ts:65](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L65) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`QueryByError`](../type-aliases/querybyerror.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/functions/querybytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: queryByText 3 | title: queryByText 4 | --- 5 | 6 | 7 | 8 | # Function: queryByText() 9 | 10 | ```ts 11 | function queryByText(...args): ReturnType> 12 | ``` 13 | 14 | Defined in: [queries/text.ts:64](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L64) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### args 23 | 24 | ...\[`TestInstance`, [`Matcher`](../../../type-aliases/matcher.md), [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md)\] 25 | 26 | ## Returns 27 | 28 | `ReturnType`\<[`QueryByText`](../type-aliases/querybytext.md)\<`T`\>\> 29 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: queries 3 | title: queries 4 | --- 5 | 6 | 7 | 8 | # queries 9 | 10 | ## Type Aliases 11 | 12 | - [FindByError](type-aliases/findbyerror.md) 13 | - [FindByText](type-aliases/findbytext.md) 14 | - [GetByError](type-aliases/getbyerror.md) 15 | - [GetByText](type-aliases/getbytext.md) 16 | - [QueryByError](type-aliases/querybyerror.md) 17 | - [QueryByText](type-aliases/querybytext.md) 18 | 19 | ## Functions 20 | 21 | - [findByError](functions/findbyerror.md) 22 | - [findByText](functions/findbytext.md) 23 | - [getByError](functions/getbyerror.md) 24 | - [getByText](functions/getbytext.md) 25 | - [queryByError](functions/querybyerror.md) 26 | - [queryByText](functions/querybytext.md) 27 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/findbyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: FindByError 3 | title: FindByError 4 | --- 5 | 6 | 7 | 8 | # Type Alias: FindByError()\ 9 | 10 | ```ts 11 | type FindByError = (instance, id, options?, waitForElementOptions?) => Promise; 12 | ``` 13 | 14 | Defined in: [queries/error.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L27) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ### waitForElementOptions? 35 | 36 | [`waitForOptions`](../../../interfaces/waitforoptions.md) 37 | 38 | ## Returns 39 | 40 | `Promise`\<`T`\> 41 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/findbytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: FindByText 3 | title: FindByText 4 | --- 5 | 6 | 7 | 8 | # Type Alias: FindByText()\ 9 | 10 | ```ts 11 | type FindByText = (instance, id, options?, waitForElementOptions?) => Promise; 12 | ``` 13 | 14 | Defined in: [queries/text.ts:27](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L27) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ### waitForElementOptions? 35 | 36 | [`waitForOptions`](../../../interfaces/waitforoptions.md) 37 | 38 | ## Returns 39 | 40 | `Promise`\<`T`\> 41 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/getbyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: GetByError 3 | title: GetByError 4 | --- 5 | 6 | 7 | 8 | # Type Alias: GetByError()\ 9 | 10 | ```ts 11 | type GetByError = (instance, id, options?) => T; 12 | ``` 13 | 14 | Defined in: [queries/error.ts:21](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L21) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ## Returns 35 | 36 | `T` 37 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/getbytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: GetByText 3 | title: GetByText 4 | --- 5 | 6 | 7 | 8 | # Type Alias: GetByText()\ 9 | 10 | ```ts 11 | type GetByText = (instance, id, options?) => T; 12 | ``` 13 | 14 | Defined in: [queries/text.ts:21](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L21) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ## Returns 35 | 36 | `T` 37 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/querybyerror.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: QueryByError 3 | title: QueryByError 4 | --- 5 | 6 | 7 | 8 | # Type Alias: QueryByError()\ 9 | 10 | ```ts 11 | type QueryByError = (instance, id, options?) => T | null; 12 | ``` 13 | 14 | Defined in: [queries/error.ts:15](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/error.ts#L15) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ## Returns 35 | 36 | `T` \| `null` 37 | -------------------------------------------------------------------------------- /docs/reference/namespaces/queries/type-aliases/querybytext.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: QueryByText 3 | title: QueryByText 4 | --- 5 | 6 | 7 | 8 | # Type Alias: QueryByText()\ 9 | 10 | ```ts 11 | type QueryByText = (instance, id, options?) => T | null; 12 | ``` 13 | 14 | Defined in: [queries/text.ts:15](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/queries/text.ts#L15) 15 | 16 | ## Type Parameters 17 | 18 | • **T** *extends* `TestInstance` = `TestInstance` 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### id 27 | 28 | [`Matcher`](../../../type-aliases/matcher.md) 29 | 30 | ### options? 31 | 32 | [`SelectorMatcherOptions`](../../../interfaces/selectormatcheroptions.md) 33 | 34 | ## Returns 35 | 36 | `T` \| `null` 37 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/boundfunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: BoundFunction 3 | title: BoundFunction 4 | --- 5 | 6 | 7 | 8 | # Type Alias: BoundFunction\ 9 | 10 | ```ts 11 | type BoundFunction = T extends (container, ...args) => infer R ? (...args) => R : never; 12 | ``` 13 | 14 | Defined in: [get-queries-for-instance.ts:4](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/get-queries-for-instance.ts#L4) 15 | 16 | ## Type Parameters 17 | 18 | • **T** 19 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/boundfunctions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: BoundFunctions 3 | title: BoundFunctions 4 | --- 5 | 6 | 7 | 8 | # Type Alias: BoundFunctions\ 9 | 10 | ```ts 11 | type BoundFunctions = TQueries extends typeof queries ? object & { [P in keyof TQueries]: BoundFunction } : { [P in keyof TQueries]: BoundFunction }; 12 | ``` 13 | 14 | Defined in: [get-queries-for-instance.ts:11](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/get-queries-for-instance.ts#L11) 15 | 16 | ## Type Parameters 17 | 18 | • **TQueries** 19 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/eventtype.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: EventType 3 | title: EventType 4 | --- 5 | 6 | 7 | 8 | # Type Alias: EventType 9 | 10 | ```ts 11 | type EventType = keyof EventMap; 12 | ``` 13 | 14 | Defined in: [events.ts:5](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/events.ts#L5) 15 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/firefunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: FireFunction 3 | title: FireFunction 4 | --- 5 | 6 | 7 | 8 | # Type Alias: FireFunction() 9 | 10 | ```ts 11 | type FireFunction = (instance, event, options?) => boolean | Promise; 12 | ``` 13 | 14 | Defined in: [events.ts:7](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/events.ts#L7) 15 | 16 | ## Type Parameters 17 | 18 | • **TEventType** *extends* [`EventType`](eventtype.md) 19 | 20 | ## Parameters 21 | 22 | ### instance 23 | 24 | `TestInstance` 25 | 26 | ### event 27 | 28 | `TEventType` 29 | 30 | ### options? 31 | 32 | `Parameters`\<`EventMap`\[`TEventType`\]\>\[`1`\] 33 | 34 | ## Returns 35 | 36 | `boolean` \| `Promise`\<`void`\> 37 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/fireobject.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: FireObject 3 | title: FireObject 4 | --- 5 | 6 | 7 | 8 | # Type Alias: FireObject 9 | 10 | ```ts 11 | type FireObject = { [K in EventType]: (instance: TestInstance, options?: Parameters[1]) => boolean | Promise }; 12 | ``` 13 | 14 | Defined in: [events.ts:13](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/events.ts#L13) 15 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/geterrorfunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: GetErrorFunction 3 | title: GetErrorFunction 4 | --- 5 | 6 | 7 | 8 | # Type Alias: GetErrorFunction()\ 9 | 10 | ```ts 11 | type GetErrorFunction = (c, ...args) => string; 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:11](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L11) 15 | 16 | ## Type Parameters 17 | 18 | • **TArguments** *extends* `any`[] = \[`string`\] 19 | 20 | ## Parameters 21 | 22 | ### c 23 | 24 | `TestInstance` | `null` 25 | 26 | ### args 27 | 28 | ...`TArguments` 29 | 30 | ## Returns 31 | 32 | `string` 33 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/match.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: Match 3 | title: Match 4 | --- 5 | 6 | 7 | 8 | # Type Alias: Match() 9 | 10 | ```ts 11 | type Match = (textToMatch, node, matcher, options?) => boolean; 12 | ``` 13 | 14 | Defined in: [matches.ts:30](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L30) 15 | 16 | ## Parameters 17 | 18 | ### textToMatch 19 | 20 | `string` 21 | 22 | ### node 23 | 24 | `TestInstance` | `null` 25 | 26 | ### matcher 27 | 28 | [`Matcher`](matcher.md) 29 | 30 | ### options? 31 | 32 | [`MatcherOptions`](../interfaces/matcheroptions.md) 33 | 34 | ## Returns 35 | 36 | `boolean` 37 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: Matcher 3 | title: Matcher 4 | --- 5 | 6 | 7 | 8 | # Type Alias: Matcher 9 | 10 | ```ts 11 | type Matcher = MatcherFunction | RegExp | number | string; 12 | ``` 13 | 14 | Defined in: [matches.ts:9](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L9) 15 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/matcherfunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: MatcherFunction 3 | title: MatcherFunction 4 | --- 5 | 6 | 7 | 8 | # Type Alias: MatcherFunction() 9 | 10 | ```ts 11 | type MatcherFunction = (content, element) => boolean; 12 | ``` 13 | 14 | Defined in: [matches.ts:4](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L4) 15 | 16 | ## Parameters 17 | 18 | ### content 19 | 20 | `string` 21 | 22 | ### element 23 | 24 | `TestInstance` | `null` 25 | 26 | ## Returns 27 | 28 | `boolean` 29 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/normalizerfn.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: NormalizerFn 3 | title: NormalizerFn 4 | --- 5 | 6 | 7 | 8 | # Type Alias: NormalizerFn() 9 | 10 | ```ts 11 | type NormalizerFn = (text) => string; 12 | ``` 13 | 14 | Defined in: [matches.ts:11](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/matches.ts#L11) 15 | 16 | ## Parameters 17 | 18 | ### text 19 | 20 | `string` 21 | 22 | ## Returns 23 | 24 | `string` 25 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/query.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: Query 3 | title: Query 4 | --- 5 | 6 | 7 | 8 | # Type Alias: Query() 9 | 10 | ```ts 11 | type Query = (container, ...args) => 12 | | Error 13 | | TestInstance 14 | | TestInstance[] 15 | | Promise 16 | | Promise 17 | | null; 18 | ``` 19 | 20 | Defined in: [get-queries-for-instance.ts:29](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/get-queries-for-instance.ts#L29) 21 | 22 | ## Parameters 23 | 24 | ### container 25 | 26 | `TestInstance` 27 | 28 | ### args 29 | 30 | ...`any`[] 31 | 32 | ## Returns 33 | 34 | \| `Error` 35 | \| `TestInstance` 36 | \| `TestInstance`[] 37 | \| `Promise`\<`TestInstance`[]\> 38 | \| `Promise`\<`TestInstance`\> 39 | \| `null` 40 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/querymethod.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: QueryMethod 3 | title: QueryMethod 4 | --- 5 | 6 | 7 | 8 | # Type Alias: QueryMethod()\ 9 | 10 | ```ts 11 | type QueryMethod = (container, ...args) => TReturn; 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:21](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L21) 15 | 16 | ## Type Parameters 17 | 18 | • **TArguments** *extends* `any`[] 19 | 20 | • **TReturn** 21 | 22 | ## Parameters 23 | 24 | ### container 25 | 26 | `TestInstance` 27 | 28 | ### args 29 | 30 | ...`TArguments` 31 | 32 | ## Returns 33 | 34 | `TReturn` 35 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/renderresult.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: RenderResult 3 | title: RenderResult 4 | --- 5 | 6 | 7 | 8 | # Type Alias: RenderResult 9 | 10 | ```ts 11 | type RenderResult = TestInstance & object & { [P in keyof typeof queries]: BoundFunction }; 12 | ``` 13 | 14 | Defined in: [pure.ts:32](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/pure.ts#L32) 15 | 16 | ## Type declaration 17 | 18 | ### userEvent 19 | 20 | ```ts 21 | userEvent: { [P in keyof UserEvent]: BoundFunction }; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/reference/type-aliases/withsuggest.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: WithSuggest 3 | title: WithSuggest 4 | --- 5 | 6 | 7 | 8 | # Type Alias: WithSuggest 9 | 10 | ```ts 11 | type WithSuggest = object; 12 | ``` 13 | 14 | Defined in: [query-helpers.ts:9](https://github.com/crutchcorn/cli-testing-library/blob/main/packages/cli-testing-library/src/query-helpers.ts#L9) 15 | 16 | ## Type declaration 17 | 18 | ### suggest? 19 | 20 | ```ts 21 | optional suggest: boolean; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/user-event.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "User Event" 3 | --- 4 | 5 | [`user-event`][gh] is a helper that provides more advanced simulation of CLI 6 | interactions than the [`fireEvent`](./fire-event) method. 7 | 8 | ## Import 9 | 10 | `userEvent` can be used either as a global import or as returned from `render`: 11 | 12 | ```javascript 13 | import {userEvent} from 'cli-testing-library' 14 | ``` 15 | 16 | Or: 17 | 18 | ```js 19 | import {render} from 'cli-testing-library' 20 | 21 | const {userEvent} = render('command') 22 | ``` 23 | 24 | ## API 25 | 26 | Note: All `userEvent` methods are synchronous with one exception: when `delay` 27 | option used with `userEvent.keyboard` as described below. We also discourage 28 | using `userEvent` inside `before/after` blocks at all, for important reasons 29 | described in 30 | ["Avoid Nesting When You're Testing"](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing). 31 | 32 | ### `keyboard(instance, text, [options])` 33 | 34 | Writes `text` inside a CLI's `stdin` buffer 35 | 36 | ```jsx 37 | import {render} from 'cli-testing-library' 38 | 39 | test('type', () => { 40 | const {getByText, userEvent} = render('command') 41 | 42 | userEvent.keyboard('Hello, World![Enter]') 43 | expect(getByText('Hello, world!')).toBeTruthy() 44 | }) 45 | ``` 46 | 47 | `options.delay` is the number of milliseconds that pass between two characters 48 | are typed. By default it's 0. You can use this option if your component has a 49 | different behavior for fast or slow users. If you do this, you need to make sure 50 | to `await`! 51 | 52 | 53 | 54 | Keystrokes can be described: 55 | 56 | - Per printable character 57 | 58 | ```js 59 | userEvent.keyboard('foo') // translates to: f, o, o 60 | ``` 61 | 62 | The bracket `[` is used as a special character and can be referenced by 63 | doubling it. 64 | 65 | ```js 66 | userEvent.keyboard('a[[') // translates to: a, [ 67 | ``` 68 | 69 | - Per [special key mapping](../src/user-event/keyboard/keyMap.ts) with the `[` 70 | symbol 71 | 72 | ```js 73 | userEvent.keyboard('[ArrowLeft][KeyF][KeyO][KeyO]') // translates to: Left Arrow, f, o, o 74 | ``` 75 | 76 | This does not keep any key pressed. So `Shift` will be lifted before pressing 77 | `f`. 78 | 79 | The mapping of special character strings are performed by a 80 | [default key map](../src/user-event/keyboard/keyMap.ts) portraying a "default" 81 | US-keyboard. You can provide your own local keyboard mapping per option. 82 | 83 | ```js 84 | userEvent.keyboard('?', {keyboardMap: myOwnLocaleKeyboardMap}) 85 | ``` 86 | 87 | 88 | 89 | #### Special characters 90 | 91 | We support inputting many special character strings with the `[` syntax 92 | mentioned previously. Here are some of the ones that are supported: 93 | 94 | | Text string | Key name | 95 | | -------------- | ----------- | 96 | | `[Enter]` | Enter | 97 | | `[Space]` | `' '` | 98 | | `[Escape]` | Escape | 99 | | `[Backspace]` | Backspace | 100 | | `[Delete]` | Delete | 101 | | `[ArrowLeft]` | Left Arrow | 102 | | `[ArrowRight]` | Right Arrow | 103 | | `[ArrowUp]` | Up Arrow | 104 | | `[ArrowDown]` | Down Arrow | 105 | | `[Home]` | Home | 106 | | `[End]` | End | 107 | 108 | A full list of supported special characters that can be input can be found 109 | [in our key mapping file](../src/user-event/keyboard/keyMap.ts). 110 | 111 | [gh]: https://github.com/testing-library/user-event 112 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | // @ts-ignore Needed due to moduleResolution Node vs Bundler 4 | import { tanstackConfig } from "@tanstack/config/eslint"; 5 | 6 | export default [ 7 | ...tanstackConfig, 8 | { 9 | name: "clitesting/temp", 10 | rules: {}, 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@5/schema.json", 3 | "ignore": ["**/tests/execute-scripts/*", "website/src/components/*"], 4 | "ignoreDependencies": [ 5 | "sharp", 6 | "@babel/runtime", 7 | "@jest/expect", 8 | "inquirer", 9 | "@types/inquirer", 10 | "@jest/globals", 11 | "vitest" 12 | ], 13 | "ignoreExportsUsedInFile": true 14 | } 15 | -------------------------------------------------------------------------------- /media/koala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crutchcorn/cli-testing-library/ffa9691659ded91372dae522fde7c7a856330566/media/koala.png -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "defaultBase": "main", 4 | "useInferencePlugins": false, 5 | "parallel": 5, 6 | "namedInputs": { 7 | "sharedGlobals": [ 8 | "{workspaceRoot}/.nvmrc", 9 | "{workspaceRoot}/package.json", 10 | "{workspaceRoot}/tsconfig.json" 11 | ], 12 | "default": [ 13 | "sharedGlobals", 14 | "{projectRoot}/**/*", 15 | "!{projectRoot}/**/*.md" 16 | ], 17 | "production": [ 18 | "default", 19 | "!{projectRoot}/tests/**/*", 20 | "!{projectRoot}/eslint.config.js" 21 | ] 22 | }, 23 | "targetDefaults": { 24 | "test:knip": { 25 | "cache": true, 26 | "inputs": ["{workspaceRoot}/**/*"] 27 | }, 28 | "test:sherif": { 29 | "cache": true, 30 | "inputs": ["{workspaceRoot}/**/package.json"] 31 | }, 32 | "test:eslint": { 33 | "cache": true, 34 | "dependsOn": ["^build"], 35 | "inputs": ["default", "^production", "{workspaceRoot}/eslint.config.js"] 36 | }, 37 | "test:lib": { 38 | "cache": true, 39 | "dependsOn": ["^build"], 40 | "inputs": ["default", "^production"], 41 | "outputs": ["{projectRoot}/coverage"] 42 | }, 43 | "test:types": { 44 | "cache": true, 45 | "dependsOn": ["^build"], 46 | "inputs": ["default", "^production"] 47 | }, 48 | "build": { 49 | "cache": true, 50 | "dependsOn": ["^build"], 51 | "inputs": ["production", "^production"], 52 | "outputs": ["{projectRoot}/build", "{projectRoot}/dist"] 53 | }, 54 | "test:build": { 55 | "cache": true, 56 | "dependsOn": ["build"], 57 | "inputs": ["production"] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/crutchcorn/cli-testing-library" 7 | }, 8 | "packageManager": "pnpm@9.14.4", 9 | "type": "module", 10 | "scripts": { 11 | "clean": "pnpm --filter \"./packages/**\" run clean", 12 | "preinstall": "node -e \"if(process.env.CI == 'true') {console.log('Skipping preinstall...')} else {process.exit(1)}\" || npx -y only-allow pnpm", 13 | "test": "pnpm run test:ci", 14 | "test:pr": "nx run-many --targets=test:sherif,test:knip,test:eslint,test:lib,test:types,test:build,build", 15 | "test:ci": "nx run-many --targets=test:sherif,test:knip,test:eslint,test:lib,test:types,test:build,build", 16 | "test:eslint": "nx run-many --target=test:eslint", 17 | "test:format": "pnpm run prettier --check", 18 | "test:sherif": "sherif", 19 | "test:lib": "nx run-many --target=test:lib --exclude=examples/**", 20 | "test:lib:dev": "pnpm run test:lib && nx watch --all -- pnpm run test:lib", 21 | "test:build": "nx run-many --target=test:build --exclude=examples/**", 22 | "test:types": "nx run-many --target=test:types --exclude=examples/**", 23 | "test:knip": "knip", 24 | "build": "nx run-many --target=build --exclude=examples/**", 25 | "build:website": "nx run-many --target=build --projects=website", 26 | "build:all": "nx run-many --target=build --exclude=examples/**", 27 | "watch": "pnpm run build:all && nx watch --all -- pnpm run build:all", 28 | "dev": "pnpm run watch", 29 | "prettier": "prettier --ignore-unknown .", 30 | "prettier:write": "pnpm run prettier --write", 31 | "docs:generate": "node scripts/generateDocs.js", 32 | "cipublish": "node scripts/publish.js", 33 | "cipublishforce": "CI=true pnpm cipublish" 34 | }, 35 | "nx": { 36 | "includedScripts": [ 37 | "test:knip", 38 | "test:sherif" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@tanstack/config": "^0.16.1", 43 | "@types/node": "^22.10.10", 44 | "eslint": "9.19.0", 45 | "knip": "^5.43.3", 46 | "nx": "^20.3.3", 47 | "premove": "^4.0.0", 48 | "prettier": "^3.4.2", 49 | "publint": "^0.3.2", 50 | "sherif": "^1.2.0", 51 | "typescript": "5.6.3", 52 | "typescript51": "npm:typescript@5.1", 53 | "typescript52": "npm:typescript@5.2", 54 | "typescript53": "npm:typescript@5.3", 55 | "typescript54": "npm:typescript@5.4", 56 | "typescript55": "npm:typescript@5.5", 57 | "vite": "^6.0.11", 58 | "vitest": "^3.0.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/cli-testing-library/README.md: -------------------------------------------------------------------------------- 1 |
2 |

CLI Testing Library

3 | 4 | 5 | koala 11 | 12 | 13 |

Simple and complete CLI testing utilities that encourage good testing 14 | practices.

15 | 16 |
17 | 18 |
19 | 20 | [![Build Status](https://img.shields.io/github/actions/workflow/status/crutchcorn/cli-testing-library/ci.yml?branch=main&style=flat-square)](https://github.com/crutchcorn/cli-testing-library/actions/workflows/validate.yml?query=branch%3Amain) 21 | [![version](https://img.shields.io/npm/v/cli-testing-library?style=flat-square)](https://www.npmjs.com/package/cli-testing-library) 22 | [![downloads](https://img.shields.io/npm/dw/cli-testing-library?style=flat-square)](https://www.npmjs.com/package/cli-testing-library) 23 | [![MIT License](https://img.shields.io/npm/l/cli-testing-library?style=flat-square)](./LICENSE) 24 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](makeapullrequest.com) 25 | [![Code of Conduct](https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square)](./CODE_OF_CONDUCT.md) 26 | 27 | 28 | 29 | [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-) 30 | 31 | 32 | 33 | [![Watch on GitHub](https://img.shields.io/github/watchers/crutchcorn/cli-testing-library.svg?style=social)](https://github.com/crutchcorn/cli-testing-library/watchers) 34 | [![Star on GitHub](https://img.shields.io/github/stars/crutchcorn/cli-testing-library.svg?style=social)](https://github.com/crutchcorn/cli-testing-library/stargazers) 35 | 36 | 37 | 38 | ## Table of Contents 39 | 40 | 41 | 42 | 43 | - [Installation](#installation) 44 | - [Usage](#usage) 45 | - [Contributors ✨](#contributors-) 46 | 47 | 48 | 49 | > This project is not affiliated with the 50 | > ["Testing Library"](https://github.com/testing-library) ecosystem that this 51 | > project is clearly inspired from. We're just big fans :) 52 | 53 | ## Installation 54 | 55 | This module is distributed via [npm][npm] which is bundled with [node][node] and 56 | should be installed as one of your project's `devDependencies`: 57 | 58 | ``` 59 | npm install --save-dev cli-testing-library 60 | ``` 61 | 62 | ## Usage 63 | 64 | > This is currently the only section of "usage" documentation. We'll be 65 | > expanding it as soon as possible 66 | 67 | Usage example: 68 | 69 | ```javascript 70 | const { resolve } = require("path"); 71 | const { render } = require("cli-testing-library"); 72 | 73 | test("Is able to make terminal input and view in-progress stdout", async () => { 74 | const { clear, findByText, queryByText, userEvent } = await render("node", [ 75 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 76 | ]); 77 | 78 | const instance = await findByText("First option"); 79 | 80 | expect(instance).toBeInTheConsole(); 81 | 82 | expect(await findByText("❯ One")).toBeInTheConsole(); 83 | 84 | clear(); 85 | 86 | userEvent("[ArrowDown]"); 87 | 88 | expect(await findByText("❯ Two")).toBeInTheConsole(); 89 | 90 | clear(); 91 | 92 | userEvent.keyboard("[Enter]"); 93 | 94 | expect(await findByText("First option: Two")).toBeInTheConsole(); 95 | expect(await queryByText("First option: Three")).not.toBeInTheConsole(); 96 | }); 97 | ``` 98 | 99 | For a API reference documentation, including suggestions on how to use this 100 | library, see our 101 | [documentation introduction with further reading](https://cli-testing.com/guides/introduction/). 102 | 103 | > While this library _does_ work in Windows, it does not appear to function 104 | > properly in Windows CI environments, such as GitHub actions. As a result, you 105 | > may need to either switch CI systems or limit your CI to only run in Linux 106 | > 107 | > If you know how to fix this, please let us know in 108 | > [this tracking issue](https://github.com/crutchcorn/cli-testing-library/issues/3) 109 | -------------------------------------------------------------------------------- /packages/cli-testing-library/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import rootConfig from "../../eslint.config.js"; 4 | 5 | export default [...rootConfig]; 6 | -------------------------------------------------------------------------------- /packages/cli-testing-library/jest-globals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-testing-library-jest-globals", 3 | "version": "3.0.0", 4 | "description": "", 5 | "type": "module", 6 | "module": "./../dist/esm/jest-globals.js", 7 | "main": "./../dist/cjs/jest-globals.cjs", 8 | "types": "./../dist/cjs/jest-globals.d.cts", 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": { 12 | "import": { 13 | "types": "./../dist/esm/jest-globals.d.ts", 14 | "default": "./../dist/esm/jest-globals.js" 15 | }, 16 | "require": { 17 | "types": "./../dist/cjs/jest-globals.d.cts", 18 | "default": "./../dist/cjs/jest-globals.cjs" 19 | } 20 | } 21 | }, 22 | "author": "Corbin Crutchley (https://crutchcorn.dev)", 23 | "license": "MIT", 24 | "sideEffects": true 25 | } 26 | -------------------------------------------------------------------------------- /packages/cli-testing-library/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-testing-library-jest", 3 | "version": "3.0.0", 4 | "description": "", 5 | "type": "module", 6 | "module": "./../dist/esm/jest.js", 7 | "main": "./../dist/cjs/jest.cjs", 8 | "types": "./../dist/cjs/jest.d.cts", 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": { 12 | "import": { 13 | "types": "./../dist/esm/jest.d.ts", 14 | "default": "./../dist/esm/jest.js" 15 | }, 16 | "require": { 17 | "types": "./../dist/cjs/jest.d.cts", 18 | "default": "./../dist/cjs/jest.cjs" 19 | } 20 | } 21 | }, 22 | "author": "Corbin Crutchley (https://crutchcorn.dev)", 23 | "license": "MIT", 24 | "sideEffects": true 25 | } 26 | -------------------------------------------------------------------------------- /packages/cli-testing-library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-testing-library", 3 | "version": "3.0.1", 4 | "description": "Simple and complete CLI testing utilities that encourage good testing practices.", 5 | "keywords": [ 6 | "testing", 7 | "cli", 8 | "unit", 9 | "integration", 10 | "functional", 11 | "end-to-end", 12 | "e2e" 13 | ], 14 | "author": "Corbin Crutchley (https://crutchcorn.dev)", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/crutchcorn/cli-testing-library", 18 | "directory": "packages/cli-testing-library" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/crutchcorn/cli-testing-library/issues" 22 | }, 23 | "homepage": "https://github.com/crutchcorn/cli-testing-library#readme", 24 | "funding": { 25 | "type": "github", 26 | "url": "https://github.com/sponsors/crutchcorn" 27 | }, 28 | "license": "MIT", 29 | "engines": { 30 | "node": ">=16" 31 | }, 32 | "scripts": { 33 | "clean": "premove ./dist ./coverage", 34 | "test:eslint": "eslint ./src ./tests", 35 | "test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"", 36 | "test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js", 37 | "test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js", 38 | "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js", 39 | "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js", 40 | "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js", 41 | "test:types:ts56": "tsc", 42 | "test:lib": "vitest", 43 | "test:lib:dev": "pnpm run test:lib --watch", 44 | "test:build": "publint --strict", 45 | "build": "vite build" 46 | }, 47 | "type": "module", 48 | "types": "dist/esm/index.d.ts", 49 | "main": "dist/cjs/index.cjs", 50 | "module": "dist/esm/index.js", 51 | "exports": { 52 | ".": { 53 | "import": { 54 | "types": "./dist/esm/index.d.ts", 55 | "default": "./dist/esm/index.js" 56 | }, 57 | "require": { 58 | "types": "./dist/cjs/index.d.cts", 59 | "default": "./dist/cjs/index.cjs" 60 | } 61 | }, 62 | "./jest": { 63 | "import": { 64 | "types": "./dist/esm/jest.d.ts", 65 | "default": "./dist/esm/jest.js" 66 | }, 67 | "require": { 68 | "types": "./dist/cjs/jest.d.cts", 69 | "default": "./dist/cjs/jest.cjs" 70 | } 71 | }, 72 | "./jest-globals": { 73 | "import": { 74 | "types": "./dist/esm/jest-globals.d.ts", 75 | "default": "./dist/esm/jest-globals.js" 76 | }, 77 | "require": { 78 | "types": "./dist/cjs/jest-globals.d.cts", 79 | "default": "./dist/cjs/jest-globals.cjs" 80 | } 81 | }, 82 | "./vitest": { 83 | "import": { 84 | "types": "./dist/esm/vitest.d.ts", 85 | "default": "./dist/esm/vitest.js" 86 | }, 87 | "require": { 88 | "types": "./dist/cjs/vitest.d.cts", 89 | "default": "./dist/cjs/vitest.cjs" 90 | } 91 | }, 92 | "./package.json": "./package.json" 93 | }, 94 | "sideEffects": false, 95 | "files": [ 96 | "dist", 97 | "src", 98 | "vitest", 99 | "jest", 100 | "jest-globals" 101 | ], 102 | "dependencies": { 103 | "@babel/code-frame": "^7.10.4", 104 | "@babel/runtime": "^7.12.5", 105 | "picocolors": "^1.1.1", 106 | "redent": "^4.0.0", 107 | "slice-ansi": "^7.1.0", 108 | "strip-ansi": "^7.1.0", 109 | "strip-final-newline": "^4.0.0", 110 | "tree-kill": "^1.2.2" 111 | }, 112 | "devDependencies": { 113 | "@jest/expect": "^29.7.0", 114 | "@jest/globals": "^29.7.0", 115 | "@types/babel__code-frame": "^7.0.6", 116 | "@types/inquirer": "^9.0.7", 117 | "@vitest/coverage-istanbul": "3.0.4", 118 | "inquirer": "^12.3.2" 119 | }, 120 | "peerDependencies": { 121 | "@jest/expect": "^29.0.0", 122 | "@jest/globals": "^29.0.0", 123 | "vitest": "^3.0.0" 124 | }, 125 | "peerDependenciesMeta": { 126 | "@jest/globals": { 127 | "optional": true 128 | }, 129 | "@jest/expect": { 130 | "optional": true 131 | }, 132 | "vitest": { 133 | "optional": true 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/config.ts: -------------------------------------------------------------------------------- 1 | import type { TestInstance } from "./types"; 2 | 3 | // import {prettyDOM} from './pretty-dom' 4 | 5 | export interface Config { 6 | /** 7 | * WARNING: `unstable` prefix means this API may change in patch and minor releases. 8 | * @param cb 9 | */ 10 | unstable_advanceTimersWrapper: ( 11 | cb: (...args: Array) => unknown, 12 | ) => unknown; 13 | asyncUtilTimeout: number; 14 | renderAwaitTime: number; 15 | errorDebounceTimeout: number; 16 | showOriginalStackTrace: boolean; 17 | throwSuggestions: boolean; 18 | getInstanceError: (message: string | null, container: TestInstance) => Error; 19 | } 20 | 21 | export interface ConfigFn { 22 | (existingConfig: Config): Partial; 23 | } 24 | 25 | type Callback = () => T; 26 | interface InternalConfig extends Config { 27 | _disableExpensiveErrorDiagnostics: boolean; 28 | } 29 | 30 | // It would be cleaner for this to live inside './queries', but 31 | // other parts of the code assume that all exports from 32 | // './queries' are query functions. 33 | let config: InternalConfig = { 34 | asyncUtilTimeout: 1000, 35 | // Short amount of time to wait for your process to spin up after a `spawn`. AFAIK There's unfortunately not much 36 | // of a better way to do this 37 | renderAwaitTime: 100, 38 | // Internal timer time to wait before attempting error recovery debounce action 39 | errorDebounceTimeout: 100, 40 | unstable_advanceTimersWrapper: (cb) => cb(), 41 | // default value for the `hidden` option in `ByRole` queries 42 | // showOriginalStackTrace flag to show the full error stack traces for async errors 43 | showOriginalStackTrace: false, 44 | 45 | // throw errors w/ suggestions for better queries. Opt in so off by default. 46 | throwSuggestions: false, 47 | 48 | // called when getBy* queries fail. (message, container) => Error 49 | getInstanceError(message, testInstance: TestInstance | undefined) { 50 | let instanceWarning = ""; 51 | if (testInstance) { 52 | const stdallArrStr = testInstance.getStdallStr(); 53 | instanceWarning = `\n${stdallArrStr}`; 54 | } else { 55 | instanceWarning = ""; 56 | } 57 | const error = new Error( 58 | [message, instanceWarning].filter(Boolean).join("\n\n"), 59 | ); 60 | error.name = "TestingLibraryElementError"; 61 | return error; 62 | }, 63 | _disableExpensiveErrorDiagnostics: false, 64 | }; 65 | 66 | export function runWithExpensiveErrorDiagnosticsDisabled( 67 | callback: Callback, 68 | ) { 69 | try { 70 | config._disableExpensiveErrorDiagnostics = true; 71 | return callback(); 72 | } finally { 73 | config._disableExpensiveErrorDiagnostics = false; 74 | } 75 | } 76 | 77 | export function configure(newConfig: ConfigFn | Partial): void { 78 | if (typeof newConfig === "function") { 79 | // Pass the existing config out to the provided function 80 | // and accept a delta in return 81 | newConfig = newConfig(config); 82 | } 83 | 84 | // Merge the incoming config delta 85 | config = { 86 | ...config, 87 | ...newConfig, 88 | }; 89 | } 90 | 91 | export function getConfig(): Config { 92 | return config; 93 | } 94 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/event-map.ts: -------------------------------------------------------------------------------- 1 | import { killProc } from "./process-helpers"; 2 | import type { TestInstance } from "./types"; 3 | 4 | const isWin = process.platform === "win32"; 5 | 6 | const eventMap = { 7 | sigterm: (instance: TestInstance): Promise => 8 | killProc(instance, isWin ? undefined : "SIGTERM"), 9 | sigkill: (instance: TestInstance): Promise => 10 | killProc(instance, isWin ? undefined : "SIGKILL"), 11 | write: (instance: TestInstance, props: { value: string }): boolean => 12 | instance.process.stdin.write(props.value), 13 | }; 14 | 15 | export { eventMap }; 16 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/events.ts: -------------------------------------------------------------------------------- 1 | import { eventMap } from "./event-map"; 2 | import type { TestInstance } from "./types"; 3 | 4 | type EventMap = typeof eventMap; 5 | export type EventType = keyof EventMap; 6 | 7 | export type FireFunction = ( 8 | instance: TestInstance, 9 | event: TEventType, 10 | options?: Parameters[1], 11 | ) => boolean | Promise; 12 | 13 | export type FireObject = { 14 | [K in EventType]: ( 15 | instance: TestInstance, 16 | options?: Parameters[1], 17 | ) => boolean | Promise; 18 | }; 19 | 20 | const fireEvent: FireFunction & FireObject = (( 21 | instance, 22 | event, 23 | props = undefined, 24 | ) => { 25 | return eventMap[event](instance, props!); 26 | }) satisfies FireFunction as never; 27 | 28 | Object.entries(eventMap).forEach(([_eventName, _eventFn]) => { 29 | const eventName = _eventName as keyof typeof eventMap; 30 | const eventFn = _eventFn as ( 31 | ...props: Array 32 | ) => ReturnType<(typeof eventMap)[keyof typeof eventMap]>; 33 | fireEvent[eventName] = (instance, ...props) => { 34 | return eventFn(instance, ...props); 35 | }; 36 | }); 37 | 38 | export { fireEvent }; 39 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/get-queries-for-instance.ts: -------------------------------------------------------------------------------- 1 | import * as defaultQueries from "./queries/index"; 2 | import type { TestInstance } from "./types"; 3 | 4 | export type BoundFunction = T extends ( 5 | container: TestInstance, 6 | ...args: infer P 7 | ) => infer R 8 | ? (...args: P) => R 9 | : never; 10 | 11 | export type BoundFunctions = TQueries extends typeof defaultQueries 12 | ? { 13 | getByText: ( 14 | ...args: Parameters>> 15 | ) => ReturnType>; 16 | queryByText: ( 17 | ...args: Parameters>> 18 | ) => ReturnType>; 19 | findByText: ( 20 | ...args: Parameters>> 21 | ) => ReturnType>; 22 | } & { 23 | [P in keyof TQueries]: BoundFunction; 24 | } 25 | : { 26 | [P in keyof TQueries]: BoundFunction; 27 | }; 28 | 29 | export type Query = ( 30 | container: TestInstance, 31 | ...args: Array 32 | ) => 33 | | Error 34 | | TestInstance 35 | | Array 36 | | Promise> 37 | | Promise 38 | | null; 39 | 40 | export interface Queries { 41 | [T: string]: Query; 42 | } 43 | 44 | /** 45 | * @param instance 46 | * @param queries object of functions 47 | * @param initialValue for reducer 48 | * @returns returns object of functions bound to container 49 | */ 50 | function getQueriesForElement( 51 | instance: TestInstance, 52 | queries: T = defaultQueries as unknown as T, 53 | initialValue = {}, 54 | ): BoundFunctions { 55 | return Object.keys(queries).reduce((helpers, key) => { 56 | const fn = queries[key]; 57 | helpers[key] = fn!.bind(null, instance); 58 | return helpers; 59 | }, initialValue as BoundFunctions); 60 | } 61 | 62 | export { getQueriesForElement }; 63 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/get-user-code-frame.ts: -------------------------------------------------------------------------------- 1 | // We try to load node dependencies 2 | import fs from "node:fs"; 3 | import pc from "picocolors"; 4 | import { codeFrameColumns } from "@babel/code-frame"; 5 | 6 | const readFileSync = fs.readFileSync; 7 | 8 | // frame has the form "at myMethod (location/to/my/file.js:10:2)" 9 | function getCodeFrame(frame: string) { 10 | const locationStart = frame.indexOf("(") + 1; 11 | const locationEnd = frame.indexOf(")"); 12 | const frameLocation = frame.slice(locationStart, locationEnd); 13 | 14 | const frameLocationElements = frameLocation.split(":"); 15 | const [filename, line, column] = [ 16 | frameLocationElements[0]!, 17 | parseInt(frameLocationElements[1]!, 10), 18 | parseInt(frameLocationElements[2]!, 10), 19 | ]; 20 | 21 | let rawFileContents = ""; 22 | try { 23 | rawFileContents = readFileSync(filename, "utf-8"); 24 | } catch (e) { 25 | return ""; 26 | } 27 | 28 | const codeFrame = codeFrameColumns( 29 | rawFileContents, 30 | { 31 | start: { line, column }, 32 | }, 33 | { 34 | highlightCode: false, 35 | linesBelow: 0, 36 | }, 37 | ); 38 | return `${pc.dim(frameLocation)}\n${codeFrame}\n`; 39 | } 40 | 41 | function getUserCodeFrame() { 42 | // If we couldn't load dependencies, we can't generate the user trace 43 | /* istanbul ignore next */ 44 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 45 | if (!readFileSync || !codeFrameColumns) { 46 | return ""; 47 | } 48 | const err = new Error(); 49 | const firstClientCodeFrame = err.stack 50 | ?.split("\n") 51 | .slice(1) // Remove first line which has the form "Error: TypeError" 52 | .find((frame) => !frame.includes("node_modules/")); // Ignore frames from 3rd party libraries 53 | 54 | return getCodeFrame(firstClientCodeFrame!); 55 | } 56 | 57 | export { getUserCodeFrame }; 58 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/helpers.ts: -------------------------------------------------------------------------------- 1 | import type { TestInstance } from "./types"; 2 | 3 | function jestFakeTimersAreEnabled() { 4 | /* istanbul ignore else */ 5 | if ( 6 | (typeof vi !== "undefined" && vi.isFakeTimers && vi.isFakeTimers()) || 7 | (typeof jest !== "undefined" && jest !== null) 8 | ) { 9 | return ( 10 | // legacy timers 11 | ( 12 | setTimeout as unknown as { 13 | _isMockFunction: boolean; 14 | } 15 | )._isMockFunction === true || 16 | // modern timers 17 | 18 | Object.prototype.hasOwnProperty.call(setTimeout, "clock") 19 | ); 20 | } 21 | // istanbul ignore next 22 | return false; 23 | } 24 | 25 | const instanceRef = { current: undefined as TestInstance | undefined }; 26 | 27 | if (typeof afterEach === "function") { 28 | afterEach(() => { 29 | instanceRef.current = undefined; 30 | }); 31 | } 32 | 33 | function getCurrentInstance() { 34 | /** 35 | * Worth mentioning that this deviates from the upstream implementation 36 | * of `dom-testing-library`'s `getDocument` in waitFor, which throws an error whenever 37 | * `window` is not defined. 38 | * 39 | * Admittedly, this is another way that `cli-testing-library` will need to figure out 40 | * the right solution to this problem, since there is no omni-present parent `instance` 41 | * in a CLI like there is in a browser. (although FWIW, "process" might work) 42 | * 43 | * Have ideas how to solve? Please let us know: 44 | * https://github.com/crutchcorn/cli-testing-library/issues/ 45 | */ 46 | return instanceRef.current; 47 | } 48 | 49 | // TODO: Does this need to be namespaced for each test that runs? 50 | // That way, we don't end up with a "singleton" that ends up wiped between 51 | // parallel tests. 52 | function setCurrentInstance(newInstance: TestInstance) { 53 | instanceRef.current = newInstance; 54 | } 55 | 56 | function debounce) => void>( 57 | func: T, 58 | timeout: number, 59 | ): (...args: Parameters) => void { 60 | let timer: ReturnType; 61 | 62 | return (...args: Parameters) => { 63 | clearTimeout(timer); 64 | timer = setTimeout(() => { 65 | // @ts-ignore this is fine 66 | func.apply(this, args); 67 | }, timeout); 68 | }; 69 | } 70 | 71 | /** 72 | * This is used to bind a series of functions where `instance` is the first argument 73 | * to an instance, removing the implicit first argument. 74 | */ 75 | function bindObjectFnsToInstance( 76 | instance: TestInstance, 77 | object: Record) => unknown>, 78 | ) { 79 | return Object.entries(object).reduce( 80 | (prev, [key, fn]) => { 81 | prev[key] = (...props: Array) => fn(instance, ...props); 82 | return prev; 83 | }, 84 | {} as typeof object, 85 | ); 86 | } 87 | 88 | export { 89 | jestFakeTimersAreEnabled, 90 | setCurrentInstance, 91 | getCurrentInstance, 92 | debounce, 93 | bindObjectFnsToInstance, 94 | }; 95 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/index.ts: -------------------------------------------------------------------------------- 1 | import { cleanup } from "./pure"; 2 | 3 | // if we're running in a test runner that supports afterEach 4 | // or teardown then we'll automatically run cleanup afterEach test 5 | // this ensures that tests run in isolation from each other 6 | // if you don't like this then set the CTL_SKIP_AUTO_CLEANUP env variable to 'true'. 7 | if ( 8 | typeof process === "undefined" || 9 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 10 | !(process.env && process.env.CTL_SKIP_AUTO_CLEANUP) 11 | ) { 12 | // ignore teardown() in code coverage because Jest does not support it 13 | /* istanbul ignore else */ 14 | if (typeof afterEach === "function") { 15 | afterEach(async () => { 16 | await cleanup(); 17 | }); 18 | } else if (typeof teardown === "function") { 19 | // Block is guarded by `typeof` check. 20 | // eslint does not support `typeof` guards. 21 | 22 | teardown(async () => { 23 | await cleanup(); 24 | }); 25 | } 26 | } 27 | 28 | export * from "./config"; 29 | export * from "./helpers"; 30 | export * from "./events"; 31 | export * from "./get-queries-for-instance"; 32 | export * from "./matches"; 33 | export * from "./pure"; 34 | export * from "./query-helpers"; 35 | export * from "./queries/index"; 36 | export * as queries from "./queries/index"; 37 | export * from "./mutation-observer"; 38 | export * from "./wait-for"; 39 | export * from "./user-event/index"; 40 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/jest-globals.ts: -------------------------------------------------------------------------------- 1 | import globals from "@jest/globals"; 2 | import * as extensions from "./matchers/index"; 3 | import type { CLITestingLibraryMatchers } from "./matchers/types"; 4 | 5 | globals.expect.extend(extensions); 6 | 7 | declare module "@jest/expect" { 8 | // eslint-disable-next-line @typescript-eslint/naming-convention 9 | export interface Matchers> 10 | extends CLITestingLibraryMatchers {} 11 | } 12 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/jest.ts: -------------------------------------------------------------------------------- 1 | import * as extensions from "./matchers/index"; 2 | import type { CLITestingLibraryMatchers } from "./matchers/types"; 3 | 4 | expect.extend(extensions); 5 | 6 | declare global { 7 | // eslint-disable-next-line @typescript-eslint/no-namespace 8 | namespace jest { 9 | // eslint-disable-next-line @typescript-eslint/naming-convention 10 | interface Matchers extends CLITestingLibraryMatchers {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matchers/index.ts: -------------------------------------------------------------------------------- 1 | import { toBeInTheConsole } from "./to-be-in-the-console"; 2 | import { toHaveErrorMessage } from "./to-have-errormessage"; 3 | 4 | export { toBeInTheConsole, toHaveErrorMessage }; 5 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matchers/to-be-in-the-console.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultNormalizer } from "../matches"; 2 | import { checkCliInstance, getMessage } from "./utils"; 3 | import type { TestInstance } from "../types"; 4 | 5 | export function toBeInTheConsole(this: any, instance: TestInstance) { 6 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 7 | if (instance !== null || !this.isNot) { 8 | checkCliInstance(instance, toBeInTheConsole, this); 9 | } 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 12 | const errormessage = instance 13 | ? getDefaultNormalizer()( 14 | instance.stdoutArr.map((obj) => obj.contents).join("\n"), 15 | ) 16 | : null; 17 | 18 | return { 19 | // Does not change based on `.not`, and as a result, we must confirm if it _actually_ is there 20 | pass: !!instance, 21 | message: () => { 22 | const to = this.isNot ? "not to" : "to"; 23 | return getMessage( 24 | this, 25 | this.utils.matcherHint( 26 | `${this.isNot ? ".not" : ""}.toBeInTheConsole`, 27 | "instance", 28 | "", 29 | ), 30 | `Expected ${to} find the instance in the console`, 31 | "", 32 | "Received", 33 | this.utils.printReceived(errormessage), 34 | ); 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matchers/to-have-errormessage.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultNormalizer } from "../matches"; 2 | import { checkCliInstance, getMessage } from "./utils"; 3 | import type { TestInstance } from "../types"; 4 | 5 | export function toHaveErrorMessage( 6 | this: any, 7 | testInstance: TestInstance, 8 | checkWith?: string | RegExp, 9 | ) { 10 | checkCliInstance(testInstance, toHaveErrorMessage, this); 11 | 12 | const expectsErrorMessage = checkWith !== undefined; 13 | 14 | const errormessage = getDefaultNormalizer()( 15 | testInstance.stderrArr.map((obj) => obj.contents).join("\n"), 16 | ); 17 | 18 | return { 19 | pass: expectsErrorMessage 20 | ? checkWith instanceof RegExp 21 | ? checkWith.test(errormessage) 22 | : this.equals(errormessage, checkWith) 23 | : Boolean(testInstance.stderrArr.length), 24 | message: () => { 25 | const to = this.isNot ? "not to" : "to"; 26 | return getMessage( 27 | this, 28 | this.utils.matcherHint( 29 | `${this.isNot ? ".not" : ""}.toHaveErrorMessage`, 30 | "instance", 31 | "", 32 | ), 33 | `Expected the instance ${to} have error message`, 34 | this.utils.printExpected(checkWith), 35 | "Received", 36 | this.utils.printReceived(errormessage), 37 | ); 38 | }, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matchers/types.ts: -------------------------------------------------------------------------------- 1 | export interface CLITestingLibraryMatchers { 2 | /** 3 | * @description 4 | * Assert whether a query is present in the console or not. 5 | * @example 6 | * expect(queryByText('Hello world')).toBeInTheDocument() 7 | */ 8 | toBeInTheConsole: () => TReturn; 9 | 10 | /** 11 | * @description 12 | * Check whether the given instance has a stderr message or not. 13 | * @example 14 | * expect(instance).toHaveErrorMessage(/command could not be found/i) // to partially match 15 | */ 16 | toHaveErrorMessage: (checkWith?: string | RegExp) => TReturn; 17 | } 18 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matchers/utils.ts: -------------------------------------------------------------------------------- 1 | import redent from "redent"; 2 | import type { TestInstance } from "../types"; 3 | 4 | class GenericTypeError extends Error { 5 | constructor( 6 | expectedString: string, 7 | received: any, 8 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 9 | matcherFn: Function, 10 | context: any, 11 | ) { 12 | super(); 13 | 14 | /* istanbul ignore next */ 15 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 16 | if (Error.captureStackTrace) { 17 | Error.captureStackTrace(this, matcherFn); 18 | } 19 | let withType = ""; 20 | try { 21 | withType = context.utils.printWithType( 22 | "Received", 23 | received, 24 | context.utils.printReceived, 25 | ); 26 | } catch (e) { 27 | // Can throw for Document: 28 | // https://github.com/jsdom/jsdom/issues/2304 29 | } 30 | this.message = [ 31 | context.utils.matcherHint( 32 | `${context.isNot ? ".not" : ""}.${matcherFn.name}`, 33 | "received", 34 | "", 35 | ), 36 | "", 37 | 38 | `${context.utils.RECEIVED_COLOR( 39 | "received", 40 | )} value must ${expectedString}.`, 41 | withType, 42 | ].join("\n"); 43 | } 44 | } 45 | 46 | type GenericTypeErrorArgs = ConstructorParameters; 47 | 48 | type AllButFirst = T extends [infer _First, ...infer Rest] ? Rest : never; 49 | 50 | class CliInstanceTypeError extends GenericTypeError { 51 | constructor(...args: AllButFirst) { 52 | super("be a TestInstance", ...args); 53 | } 54 | } 55 | 56 | type CliInstanceTypeErrorArgs = ConstructorParameters< 57 | typeof CliInstanceTypeError 58 | >; 59 | 60 | function checkCliInstance( 61 | cliInstance: TestInstance, 62 | ...args: AllButFirst 63 | ) { 64 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 65 | if (!(cliInstance && cliInstance.process && cliInstance.process.stdout)) { 66 | throw new CliInstanceTypeError(cliInstance, ...args); 67 | } 68 | } 69 | 70 | function display(context: any, value: any) { 71 | return typeof value === "string" ? value : context.utils.stringify(value); 72 | } 73 | 74 | function getMessage( 75 | context: any, 76 | matcher: string, 77 | expectedLabel: string, 78 | expectedValue: string, 79 | receivedLabel: string, 80 | receivedValue: string, 81 | ) { 82 | return [ 83 | `${matcher}\n`, 84 | `${expectedLabel}:\n${context.utils.EXPECTED_COLOR( 85 | redent(display(context, expectedValue), 2), 86 | )}`, 87 | `${receivedLabel}:\n${context.utils.RECEIVED_COLOR( 88 | redent(display(context, receivedValue), 2), 89 | )}`, 90 | ].join("\n"); 91 | } 92 | 93 | export { CliInstanceTypeError, checkCliInstance, getMessage }; 94 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/matches.ts: -------------------------------------------------------------------------------- 1 | import stripAnsiFn from "strip-ansi"; 2 | import type { TestInstance } from "./types"; 3 | 4 | export type MatcherFunction = ( 5 | content: string, 6 | element: TestInstance | null, 7 | ) => boolean; 8 | 9 | export type Matcher = MatcherFunction | RegExp | number | string; 10 | 11 | export type NormalizerFn = (text: string) => string; 12 | 13 | export interface NormalizerOptions extends DefaultNormalizerOptions { 14 | normalizer?: NormalizerFn; 15 | } 16 | 17 | export interface MatcherOptions { 18 | exact?: boolean; 19 | /** Use normalizer with getDefaultNormalizer instead */ 20 | trim?: boolean; 21 | /** Use normalizer with getDefaultNormalizer instead */ 22 | stripAnsi?: boolean; 23 | /** Use normalizer with getDefaultNormalizer instead */ 24 | collapseWhitespace?: boolean; 25 | normalizer?: NormalizerFn; 26 | /** suppress suggestions for a specific query */ 27 | suggest?: boolean; 28 | } 29 | 30 | export type Match = ( 31 | textToMatch: string, 32 | node: TestInstance | null, 33 | matcher: Matcher, 34 | options?: MatcherOptions, 35 | ) => boolean; 36 | 37 | export interface DefaultNormalizerOptions { 38 | trim?: boolean; 39 | collapseWhitespace?: boolean; 40 | stripAnsi?: boolean; 41 | } 42 | 43 | function assertNotNullOrUndefined(matcher: Matcher) { 44 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 45 | if (matcher === null || matcher === undefined) { 46 | throw new Error( 47 | `It looks like ${matcher} was passed instead of a matcher. Did you do something like getByText(${matcher})?`, 48 | ); 49 | } 50 | } 51 | 52 | /** 53 | * @private 54 | */ 55 | function fuzzyMatches( 56 | textToMatch: string, 57 | node: TestInstance | null, 58 | matcher: Matcher, 59 | normalizer: NormalizerFn, 60 | ) { 61 | if (typeof textToMatch !== "string") { 62 | return false; 63 | } 64 | assertNotNullOrUndefined(matcher); 65 | 66 | const normalizedText = normalizer(textToMatch); 67 | 68 | if (typeof matcher === "string" || typeof matcher === "number") { 69 | return normalizedText 70 | .toLowerCase() 71 | .includes(matcher.toString().toLowerCase()); 72 | } else if (typeof matcher === "function") { 73 | return matcher(normalizedText, node); 74 | } else { 75 | return matcher.test(normalizedText); 76 | } 77 | } 78 | 79 | /** 80 | * @private 81 | */ 82 | function matches( 83 | textToMatch: string, 84 | node: TestInstance | null, 85 | matcher: Matcher, 86 | normalizer: NormalizerFn, 87 | ): boolean { 88 | if (typeof textToMatch !== "string") { 89 | return false; 90 | } 91 | 92 | assertNotNullOrUndefined(matcher); 93 | 94 | const normalizedText = normalizer(textToMatch); 95 | if (matcher instanceof Function) { 96 | return matcher(normalizedText, node); 97 | } else if (matcher instanceof RegExp) { 98 | return matcher.test(normalizedText); 99 | } else { 100 | return normalizedText === String(matcher); 101 | } 102 | } 103 | 104 | function getDefaultNormalizer({ 105 | trim = true, 106 | collapseWhitespace = true, 107 | stripAnsi = true, 108 | }: DefaultNormalizerOptions = {}): NormalizerFn { 109 | return (text: string) => { 110 | let normalizedText = text; 111 | normalizedText = trim ? normalizedText.trim() : normalizedText; 112 | normalizedText = collapseWhitespace 113 | ? normalizedText.replace(/\s+/g, " ") 114 | : normalizedText; 115 | normalizedText = stripAnsi ? stripAnsiFn(normalizedText) : normalizedText; 116 | return normalizedText; 117 | }; 118 | } 119 | 120 | /** 121 | * @param {Object} props 122 | * Constructs a normalizer to pass to functions in matches.js 123 | * @param {boolean|undefined} props.trim The user-specified value for `trim`, without 124 | * any defaulting having been applied 125 | * @param {boolean|undefined} props.stripAnsi The user-specified value for `stripAnsi`, without 126 | * any defaulting having been applied 127 | * @param {boolean|undefined} props.collapseWhitespace The user-specified value for 128 | * `collapseWhitespace`, without any defaulting having been applied 129 | * @param {Function|undefined} props.normalizer The user-specified normalizer 130 | * @returns {Function} A normalizer 131 | */ 132 | function makeNormalizer({ 133 | trim, 134 | stripAnsi, 135 | collapseWhitespace, 136 | normalizer, 137 | }: NormalizerOptions): NormalizerFn { 138 | if (normalizer) { 139 | // User has specified a custom normalizer 140 | if ( 141 | typeof trim !== "undefined" || 142 | typeof collapseWhitespace !== "undefined" || 143 | typeof stripAnsi !== "undefined" 144 | ) { 145 | // They've also specified a value for trim or collapseWhitespace 146 | throw new Error( 147 | "trim and collapseWhitespace are not supported with a normalizer. " + 148 | "If you want to use the default trim and collapseWhitespace logic in your normalizer, " + 149 | 'use "getDefaultNormalizer({trim, collapseWhitespace})" and compose that into your normalizer', 150 | ); 151 | } 152 | 153 | return normalizer; 154 | } else { 155 | // No custom normalizer specified. Just use default. 156 | return getDefaultNormalizer({ trim, collapseWhitespace, stripAnsi }); 157 | } 158 | } 159 | 160 | export { fuzzyMatches, matches, getDefaultNormalizer, makeNormalizer }; 161 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/mutation-observer.ts: -------------------------------------------------------------------------------- 1 | // Used for `MutationObserver`. Unsure if it's really needed, but it's worth mentioning that these are not tied to 2 | // specific CLI instances of `render`. This means that if there are e2e CLI tests that run in parallel, they will 3 | // execute far more frequently than needed. 4 | const _observers = new Map(); 5 | 6 | // Not perfect as a way to make "MutationObserver" unique IDs, but it should work 7 | let mutId = 0; 8 | 9 | class MutationObserver { 10 | _cb: () => void; 11 | _id: number; 12 | 13 | constructor(cb: () => void) { 14 | this._id = ++mutId; 15 | this._cb = cb; 16 | } 17 | 18 | observe() { 19 | _observers.set(this._id, this._cb); 20 | } 21 | 22 | disconnect() { 23 | _observers.delete(this._id); 24 | } 25 | } 26 | 27 | function _runObservers() { 28 | Array.from(_observers.values()).forEach((cb) => cb()); 29 | } 30 | 31 | export { _runObservers, MutationObserver }; 32 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/pretty-cli.ts: -------------------------------------------------------------------------------- 1 | import sliceAnsi from "slice-ansi"; 2 | import { getUserCodeFrame } from "./get-user-code-frame"; 3 | import type { TestInstance } from "./types"; 4 | 5 | function prettyCLI(testInstance: TestInstance, maxLength?: number) { 6 | if (typeof maxLength !== "number") { 7 | maxLength = 8 | (typeof process !== "undefined" && 9 | Number(process.env.DEBUG_PRINT_LIMIT)) || 10 | 7000; 11 | } 12 | 13 | if (maxLength === 0) { 14 | return ""; 15 | } 16 | 17 | if (!("stdoutArr" in testInstance && "stderrArr" in testInstance)) { 18 | throw new TypeError(`Expected an instance but got ${testInstance}`); 19 | } 20 | 21 | const outStr = testInstance.getStdallStr(); 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 24 | return maxLength !== undefined && outStr.length > maxLength 25 | ? sliceAnsi(outStr, 0, maxLength) 26 | : outStr; 27 | } 28 | 29 | const logCLI = (...args: Parameters) => { 30 | const userCodeFrame = getUserCodeFrame(); 31 | if (userCodeFrame) { 32 | process.stdout.write(`${prettyCLI(...args)}\n\n${userCodeFrame}`); 33 | } else { 34 | process.stdout.write(prettyCLI(...args)); 35 | } 36 | }; 37 | 38 | export { prettyCLI, logCLI }; 39 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/process-helpers.ts: -------------------------------------------------------------------------------- 1 | import treeKill from "tree-kill"; 2 | import { getConfig } from "./config"; 3 | import type { TestInstance } from "./types"; 4 | 5 | export const killProc = (instance: TestInstance, signal: string | undefined) => 6 | new Promise((resolve, reject) => { 7 | if (!instance.process.pid || (instance.process.pid && instance.hasExit())) { 8 | resolve(); 9 | return; 10 | } 11 | 12 | treeKill(instance.process.pid, signal, async (err) => { 13 | try { 14 | if (err) { 15 | if ( 16 | err.message.includes("The process") && 17 | err.message.includes("not found.") 18 | ) { 19 | resolve(); 20 | return; 21 | } 22 | if ( 23 | err.message.includes("could not be terminated") && 24 | err.message.includes("There is no running instance of the task.") && 25 | instance.hasExit() 26 | ) { 27 | resolve(); 28 | return; 29 | } 30 | const isOperationNotSupported = err.message.includes( 31 | "The operation attempted is not supported.", 32 | ); 33 | const isAccessDenied = err.message.includes("Access is denied."); 34 | if ( 35 | err.message.includes("could not be terminated") && 36 | (isOperationNotSupported || isAccessDenied) 37 | ) { 38 | const sleep = (t: number) => new Promise((r) => setTimeout(r, t)); 39 | await sleep(getConfig().errorDebounceTimeout); 40 | if (instance.hasExit()) { 41 | resolve(); 42 | return; 43 | } 44 | console.warn("Ran into error while trying to kill process:"); 45 | console.warn(err.toString()); 46 | console.warn(`This is likely due to Window's permissions. 47 | Because this error is prevalent on CI Windows systems with the tree-kill package, we are attempting 48 | an alternative kill method.`); 49 | console.warn(); 50 | console.warn( 51 | "Be aware that this alternative kill method is not guaranteed to work with subprocesses, and they may not exit properly as a result.", 52 | ); 53 | 54 | const didKill = instance.process.kill(signal as "SIGKILL"); 55 | if (didKill) { 56 | resolve(); 57 | } else { 58 | console.error( 59 | "Alternative kill method failed. Rejecting with original error.", 60 | ); 61 | reject(err); 62 | } 63 | return; 64 | } 65 | reject(err); 66 | } else resolve(); 67 | } catch (e: unknown) { 68 | reject(e); 69 | } 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/pure.ts: -------------------------------------------------------------------------------- 1 | import childProcess from "node:child_process"; 2 | import { performance } from "node:perf_hooks"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import stripFinalNewline from "strip-final-newline"; 6 | import { _runObservers } from "./mutation-observer"; 7 | import { getQueriesForElement } from "./get-queries-for-instance"; 8 | import userEvent from "./user-event/index"; 9 | import { bindObjectFnsToInstance, setCurrentInstance } from "./helpers"; 10 | import { fireEvent } from "./events"; 11 | import { getConfig } from "./config"; 12 | import { logCLI } from "./pretty-cli"; 13 | import type { TestInstance } from "./types"; 14 | import type * as queries from "./queries/index"; 15 | import type { SpawnOptionsWithoutStdio } from "node:child_process"; 16 | import type { BoundFunction } from "./get-queries-for-instance"; 17 | 18 | const __curDir = 19 | typeof __dirname === "undefined" 20 | ? // @ts-ignore ESM requires this, but it doesn't work in Node18 21 | path.dirname(fileURLToPath(import.meta.url)) 22 | : __dirname; 23 | 24 | export interface RenderOptions { 25 | cwd: string; 26 | debug: boolean; 27 | spawnOpts: Omit; 28 | } 29 | 30 | type UserEvent = typeof userEvent; 31 | 32 | export type RenderResult = TestInstance & { 33 | userEvent: { 34 | [P in keyof UserEvent]: BoundFunction; 35 | }; 36 | } & { [P in keyof typeof queries]: BoundFunction<(typeof queries)[P]> }; 37 | 38 | const mountedInstances = new Set(); 39 | 40 | async function render( 41 | command: string, 42 | args: Array = [], 43 | opts: Partial = {}, 44 | ): Promise { 45 | const { cwd = __curDir, spawnOpts = {} } = opts; 46 | 47 | const exec = childProcess.spawn(command, args, { 48 | ...spawnOpts, 49 | cwd, 50 | shell: true, 51 | }); 52 | 53 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 54 | let _readyPromiseInternals: null | { resolve: Function; reject: Function } = 55 | null; 56 | 57 | let _resolved = false; 58 | 59 | const execOutputAPI = { 60 | __exitCode: null as null | number, 61 | _isOutputAPI: true, 62 | _isReady: new Promise( 63 | (resolve, reject) => (_readyPromiseInternals = { resolve, reject }), 64 | ), 65 | process: exec, 66 | // Clear buffer of stdout to do more accurate `t.regex` checks 67 | clear() { 68 | execOutputAPI.stdoutArr = []; 69 | execOutputAPI.stderrArr = []; 70 | }, 71 | debug(maxLength?: number) { 72 | logCLI(execOutputAPI, maxLength); 73 | }, 74 | // An array of strings gathered from stdout when unable to do 75 | // `await stdout` because of inquirer interactive prompts 76 | stdoutArr: [] as Array<{ contents: Buffer | string; timestamp: number }>, 77 | stderrArr: [] as Array<{ contents: Buffer | string; timestamp: number }>, 78 | hasExit() { 79 | return this.__exitCode === null ? null : { exitCode: this.__exitCode }; 80 | }, 81 | getStdallStr(): string { 82 | return this.stderrArr 83 | .concat(this.stdoutArr) 84 | .sort((a, b) => (a.timestamp < b.timestamp ? -1 : 1)) 85 | .map((obj) => obj.contents) 86 | .join("\n"); 87 | }, 88 | } as TestInstance & { 89 | __exitCode: null | number; 90 | _isOutputAPI: true; 91 | _isReady: Promise; 92 | }; 93 | 94 | mountedInstances.add(execOutputAPI as unknown as TestInstance); 95 | 96 | exec.stdout.on("data", (result: string | Buffer) => { 97 | // `on('spawn') doesn't work the same way in Node12. 98 | // Instead, we have to rely on this working as-expected. 99 | if (_readyPromiseInternals && !_resolved) { 100 | _readyPromiseInternals.resolve(); 101 | _resolved = true; 102 | } 103 | 104 | const resStr = stripFinalNewline(result as string); 105 | execOutputAPI.stdoutArr.push({ 106 | contents: resStr, 107 | timestamp: performance.now(), 108 | }); 109 | _runObservers(); 110 | }); 111 | 112 | exec.stderr.on("data", (result: string | Buffer) => { 113 | if (_readyPromiseInternals && !_resolved) { 114 | _readyPromiseInternals.resolve(); 115 | _resolved = true; 116 | } 117 | 118 | const resStr = stripFinalNewline(result as string); 119 | execOutputAPI.stderrArr.push({ 120 | contents: resStr, 121 | timestamp: performance.now(), 122 | }); 123 | _runObservers(); 124 | }); 125 | 126 | exec.on("error", (result) => { 127 | if (_readyPromiseInternals) { 128 | _readyPromiseInternals.reject(result); 129 | } 130 | }); 131 | 132 | exec.on("spawn", () => { 133 | setTimeout(() => { 134 | if (_readyPromiseInternals && !_resolved) { 135 | _readyPromiseInternals.resolve(); 136 | _resolved = true; 137 | } 138 | }, getConfig().renderAwaitTime); 139 | }); 140 | 141 | exec.on("exit", (code) => { 142 | execOutputAPI.__exitCode = code ?? 0; 143 | }); 144 | 145 | setCurrentInstance(execOutputAPI); 146 | 147 | await execOutputAPI._isReady; 148 | 149 | function getStdallStr(this: Omit) { 150 | return this.stderrArr 151 | .concat(this.stdoutArr) 152 | .sort((a, b) => (a.timestamp < b.timestamp ? -1 : 1)) 153 | .map((obj) => obj.contents) 154 | .join("\n"); 155 | } 156 | 157 | return Object.assign( 158 | execOutputAPI, 159 | { 160 | userEvent: bindObjectFnsToInstance(execOutputAPI, userEvent as never), 161 | getStdallStr: getStdallStr.bind(execOutputAPI), 162 | }, 163 | getQueriesForElement(execOutputAPI), 164 | ) as TestInstance as RenderResult; 165 | } 166 | 167 | function cleanup() { 168 | return Promise.all(Array.from(mountedInstances).map(cleanupAtInstance)); 169 | } 170 | 171 | // maybe one day we'll expose this (perhaps even as a utility returned by render). 172 | // but let's wait until someone asks for it. 173 | async function cleanupAtInstance(instance: TestInstance) { 174 | await fireEvent.sigkill(instance); 175 | 176 | mountedInstances.delete(instance); 177 | } 178 | 179 | export { render, cleanup }; 180 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/queries/all-utils.ts: -------------------------------------------------------------------------------- 1 | export * from "../matches"; 2 | export * from "../query-helpers"; 3 | export * from "../config"; 4 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/queries/error.ts: -------------------------------------------------------------------------------- 1 | import { 2 | buildQueries, 3 | fuzzyMatches, 4 | makeNormalizer, 5 | matches, 6 | } from "./all-utils"; 7 | import type { 8 | GetErrorFunction, 9 | Matcher, 10 | SelectorMatcherOptions, 11 | } from "./all-utils"; 12 | import type { TestInstance } from "../types"; 13 | import type { waitForOptions } from "../wait-for"; 14 | 15 | export type QueryByError = ( 16 | instance: TestInstance, 17 | id: Matcher, 18 | options?: SelectorMatcherOptions, 19 | ) => T | null; 20 | 21 | export type GetByError = ( 22 | instance: TestInstance, 23 | id: Matcher, 24 | options?: SelectorMatcherOptions, 25 | ) => T; 26 | 27 | export type FindByError = ( 28 | instance: TestInstance, 29 | id: Matcher, 30 | options?: SelectorMatcherOptions, 31 | waitForElementOptions?: waitForOptions, 32 | ) => Promise; 33 | 34 | const queryByErrorBase: QueryByError = ( 35 | instance, 36 | text, 37 | { exact = false, collapseWhitespace, trim, normalizer, stripAnsi } = {}, 38 | ) => { 39 | const matcher = exact ? matches : fuzzyMatches; 40 | const matchNormalizer = makeNormalizer({ 41 | stripAnsi, 42 | collapseWhitespace, 43 | trim, 44 | normalizer, 45 | }); 46 | const str = instance.stderrArr.map((obj) => obj.contents).join("\n"); 47 | if (matcher(str, instance, text, matchNormalizer)) return instance; 48 | else return null; 49 | }; 50 | 51 | const getMissingError: GetErrorFunction<[unknown]> = (_c, text) => 52 | `Unable to find an stdout line with the text: ${text}. This could be because the text is broken up by multiple lines. In this case, you can provide a function for your text matcher to make your matcher more flexible.`; 53 | 54 | const [_queryByErrorWithSuggestions, _getByError, _findByError] = buildQueries( 55 | queryByErrorBase, 56 | getMissingError, 57 | ); 58 | 59 | export function getByError( 60 | ...args: Parameters> 61 | ): ReturnType> { 62 | return _getByError(...args); 63 | } 64 | 65 | export function queryByError( 66 | ...args: Parameters> 67 | ): ReturnType> { 68 | return _queryByErrorWithSuggestions(...args); 69 | } 70 | 71 | export function findByError( 72 | ...args: Parameters> 73 | ): ReturnType> { 74 | return _findByError(...args); 75 | } 76 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./text"; 2 | export * from "./error"; 3 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/queries/text.ts: -------------------------------------------------------------------------------- 1 | import { 2 | buildQueries, 3 | fuzzyMatches, 4 | makeNormalizer, 5 | matches, 6 | } from "./all-utils"; 7 | import type { TestInstance } from "../types"; 8 | import type { 9 | GetErrorFunction, 10 | Matcher, 11 | SelectorMatcherOptions, 12 | } from "./all-utils"; 13 | import type { waitForOptions } from "../wait-for"; 14 | 15 | export type QueryByText = ( 16 | instance: TestInstance, 17 | id: Matcher, 18 | options?: SelectorMatcherOptions, 19 | ) => T | null; 20 | 21 | export type GetByText = ( 22 | instance: TestInstance, 23 | id: Matcher, 24 | options?: SelectorMatcherOptions, 25 | ) => T; 26 | 27 | export type FindByText = ( 28 | instance: TestInstance, 29 | id: Matcher, 30 | options?: SelectorMatcherOptions, 31 | waitForElementOptions?: waitForOptions, 32 | ) => Promise; 33 | 34 | const queryByTextBase: QueryByText = ( 35 | instance, 36 | text, 37 | { exact = false, collapseWhitespace, trim, normalizer, stripAnsi } = {}, 38 | ) => { 39 | const matcher = exact ? matches : fuzzyMatches; 40 | const matchNormalizer = makeNormalizer({ 41 | stripAnsi, 42 | collapseWhitespace, 43 | trim, 44 | normalizer, 45 | }); 46 | const str = instance.stdoutArr.map((output) => output.contents).join("\n"); 47 | if (matcher(str, instance, text, matchNormalizer)) return instance; 48 | else return null; 49 | }; 50 | 51 | const getMissingError: GetErrorFunction<[unknown]> = (_c, text) => 52 | `Unable to find an stdout line with the text: ${text}. This could be because the text is broken up by multiple lines. In this case, you can provide a function for your text matcher to make your matcher more flexible.`; 53 | 54 | const [_queryByTextWithSuggestions, _getByText, _findByText] = buildQueries( 55 | queryByTextBase, 56 | getMissingError, 57 | ); 58 | 59 | export function getByText( 60 | ...args: Parameters> 61 | ): ReturnType> { 62 | return _getByText(...args); 63 | } 64 | export function queryByText( 65 | ...args: Parameters> 66 | ): ReturnType> { 67 | return _queryByTextWithSuggestions(...args); 68 | } 69 | export function findByText( 70 | ...args: Parameters> 71 | ): ReturnType> { 72 | return _findByText(...args); 73 | } 74 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/query-helpers.ts: -------------------------------------------------------------------------------- 1 | import { getSuggestedQuery } from "./suggestions"; 2 | import { waitFor } from "./wait-for"; 3 | import { getConfig } from "./config"; 4 | import type { waitForOptions as WaitForOptions } from "./wait-for"; 5 | import type { Variant } from "./suggestions"; 6 | import type { TestInstance } from "./types"; 7 | import type { Matcher, MatcherOptions } from "./matches"; 8 | 9 | export type WithSuggest = { suggest?: boolean }; 10 | 11 | export type GetErrorFunction = [string]> = ( 12 | c: TestInstance | null, 13 | ...args: TArguments 14 | ) => string; 15 | 16 | export interface SelectorMatcherOptions extends MatcherOptions { 17 | selector?: string; 18 | ignore?: boolean | string; 19 | } 20 | 21 | export type QueryMethod, TReturn> = ( 22 | container: TestInstance, 23 | ...args: TArguments 24 | ) => TReturn; 25 | 26 | function getInstanceError(message: string | null, instance: TestInstance) { 27 | return getConfig().getInstanceError(message, instance); 28 | } 29 | 30 | function getSuggestionError( 31 | suggestion: { toString: () => string }, 32 | container: TestInstance, 33 | ) { 34 | return getConfig().getInstanceError( 35 | `A better query is available, try this: 36 | ${suggestion.toString()} 37 | `, 38 | container, 39 | ); 40 | } 41 | 42 | // this accepts a query function and returns a function which throws an error 43 | // if an empty list of elements is returned 44 | function makeGetQuery>( 45 | queryBy: (instance: TestInstance, ...args: TArguments) => TestInstance | null, 46 | getMissingError: GetErrorFunction, 47 | ) { 48 | return ( 49 | instance: TestInstance, 50 | ...args: TArguments 51 | ): T => { 52 | const el = queryBy(instance, ...args); 53 | if (!el) { 54 | throw getConfig().getInstanceError( 55 | getMissingError(instance, ...args), 56 | instance, 57 | ); 58 | } 59 | 60 | return el as T; 61 | }; 62 | } 63 | 64 | // this accepts a getter query function and returns a function which calls 65 | // waitFor and passing a function which invokes the getter. 66 | function makeFindQuery( 67 | getter: ( 68 | container: TestInstance, 69 | text: Matcher, 70 | options?: MatcherOptions, 71 | ) => TQueryFor, 72 | ) { 73 | return ( 74 | instance: TestInstance, 75 | text: Matcher, 76 | options?: MatcherOptions, 77 | waitForOptions?: WaitForOptions, 78 | ): Promise => { 79 | return waitFor( 80 | () => { 81 | return getter(instance, text, options) as unknown as T; 82 | }, 83 | { instance, ...waitForOptions }, 84 | ); 85 | }; 86 | } 87 | 88 | const wrapSingleQueryWithSuggestion = 89 | >( 90 | query: ( 91 | container: TestInstance, 92 | ...args: TArguments 93 | ) => TestInstance | null, 94 | queryByName: string, 95 | variant: Variant, 96 | ) => 97 | ( 98 | container: TestInstance, 99 | ...args: TArguments 100 | ): T => { 101 | const instance = query(container, ...args); 102 | const [{ suggest = getConfig().throwSuggestions } = {}] = args.slice( 103 | -1, 104 | ) as [WithSuggest]; 105 | if (instance && suggest) { 106 | const suggestion = getSuggestedQuery(instance, variant); 107 | if (suggestion && !queryByName.endsWith(suggestion.queryName)) { 108 | throw getSuggestionError(suggestion.toString(), container); 109 | } 110 | } 111 | 112 | return instance as T; 113 | }; 114 | 115 | function buildQueries( 116 | queryBy: QueryMethod< 117 | [matcher: Matcher, options?: MatcherOptions], 118 | TestInstance | null 119 | >, 120 | getMissingError: GetErrorFunction< 121 | [matcher: Matcher, options?: MatcherOptions] 122 | >, 123 | ) { 124 | const getBy = makeGetQuery(queryBy, getMissingError); 125 | 126 | const queryByWithSuggestions = wrapSingleQueryWithSuggestion( 127 | queryBy, 128 | queryBy.name, 129 | "get", 130 | ); 131 | 132 | const getByWithSuggestions = wrapSingleQueryWithSuggestion( 133 | getBy, 134 | queryBy.name, 135 | "get", 136 | ); 137 | 138 | const findBy = makeFindQuery( 139 | wrapSingleQueryWithSuggestion(getBy, queryBy.name, "find"), 140 | ); 141 | 142 | return [queryByWithSuggestions, getByWithSuggestions, findBy] as const; 143 | } 144 | 145 | export { 146 | getInstanceError, 147 | wrapSingleQueryWithSuggestion, 148 | makeFindQuery, 149 | buildQueries, 150 | }; 151 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/suggestions.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultNormalizer } from "./matches"; 2 | import type { TestInstance } from "./types"; 3 | 4 | export interface QueryOptions { 5 | [key: string]: RegExp | boolean; 6 | } 7 | 8 | export type QueryArgs = [string, QueryOptions?]; 9 | 10 | export interface Suggestion { 11 | queryName: string; 12 | queryMethod: string; 13 | queryArgs: QueryArgs; 14 | variant: string; 15 | warning?: string; 16 | toString: () => string; 17 | } 18 | 19 | export type Variant = "find" | "get" | "query"; 20 | 21 | export type Method = "Text" | "text"; 22 | 23 | const normalize = getDefaultNormalizer(); 24 | 25 | function escapeRegExp(string: string) { 26 | return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string 27 | } 28 | 29 | function getRegExpMatcher(string: string) { 30 | return new RegExp(escapeRegExp(string.toLowerCase()), "i"); 31 | } 32 | 33 | function makeSuggestion( 34 | queryName: string, 35 | _instance: TestInstance, 36 | content: string, 37 | { 38 | variant, 39 | name, 40 | }: { 41 | variant: Variant; 42 | name?: string; 43 | }, 44 | ): Suggestion | undefined { 45 | const warning = "" as string; 46 | const queryOptions = {} as QueryOptions; 47 | const queryArgs = [ 48 | [].includes(queryName as never) ? content : getRegExpMatcher(content), 49 | ] as QueryArgs; 50 | 51 | if (name) { 52 | queryOptions.name = getRegExpMatcher(name); 53 | } 54 | 55 | if (Object.keys(queryOptions).length > 0) { 56 | queryArgs.push(queryOptions); 57 | } 58 | 59 | const queryMethod = `${variant}By${queryName}`; 60 | 61 | return { 62 | queryName, 63 | queryMethod, 64 | queryArgs, 65 | variant, 66 | warning, 67 | toString() { 68 | if (warning) { 69 | console.warn(warning); 70 | } 71 | const [text, options] = queryArgs; 72 | 73 | const newText = typeof text === "string" ? `'${text}'` : text; 74 | 75 | const newOptions = options 76 | ? `, { ${Object.entries(options) 77 | .map(([k, v]) => `${k}: ${v}`) 78 | .join(", ")} }` 79 | : ""; 80 | 81 | return `${queryMethod}(${newText}${newOptions})`; 82 | }, 83 | }; 84 | } 85 | 86 | function canSuggest( 87 | currentMethod: Method, 88 | requestedMethod: Method | undefined, 89 | data: unknown, 90 | ) { 91 | return ( 92 | data && 93 | (!requestedMethod || 94 | requestedMethod.toLowerCase() === currentMethod.toLowerCase()) 95 | ); 96 | } 97 | 98 | export function getSuggestedQuery( 99 | instance: TestInstance, 100 | variant: Variant = "get", 101 | method?: Method, 102 | ): Suggestion | undefined { 103 | const textContent = normalize( 104 | instance.stdoutArr.map((obj) => obj.contents).join("\n"), 105 | ); 106 | if (canSuggest("Text", method, textContent)) { 107 | return makeSuggestion("Text", instance, textContent, { variant }); 108 | } 109 | 110 | return undefined; 111 | } 112 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ChildProcessWithoutNullStreams } from "node:child_process"; 2 | 3 | export interface TestInstance { 4 | clear: () => void; 5 | process: ChildProcessWithoutNullStreams; 6 | stdoutArr: Array<{ contents: Buffer | string; timestamp: number }>; 7 | stderrArr: Array<{ contents: Buffer | string; timestamp: number }>; 8 | getStdallStr: () => string; 9 | hasExit: () => null | { exitCode: number }; 10 | debug: (maxLength?: number) => void; 11 | } 12 | 13 | declare global { 14 | const jest: undefined | any; 15 | const vi: undefined | any; 16 | const afterEach: undefined | ((fn: () => void) => void); 17 | const teardown: undefined | ((fn: () => void) => void); 18 | const expect: undefined | any; 19 | } 20 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/index.ts: -------------------------------------------------------------------------------- 1 | import { keyboard } from "./keyboard/index"; 2 | 3 | const userEvent = { 4 | keyboard, 5 | }; 6 | 7 | export default userEvent; 8 | 9 | export type { keyboardKey } from "./keyboard/index"; 10 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/keyboard/getNextKeyDef.ts: -------------------------------------------------------------------------------- 1 | import type { keyboardKey, keyboardOptions } from "./types"; 2 | 3 | enum bracketDict { 4 | "[" = "]", 5 | } 6 | 7 | /** 8 | * Get the next key from keyMap 9 | * 10 | * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`. 11 | * Everything else will be interpreted as a typed character - e.g. `a`. 12 | * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`. 13 | */ 14 | export function getNextKeyDef( 15 | text: string, 16 | options: keyboardOptions, 17 | ): { 18 | keyDef: keyboardKey; 19 | consumedLength: number; 20 | } { 21 | const { type, descriptor, consumedLength } = readNextDescriptor(text); 22 | 23 | const keyDef: keyboardKey = options.keyboardMap.find((def) => { 24 | if (type === "[") { 25 | return def.code?.toLowerCase() === descriptor.toLowerCase(); 26 | } 27 | return def.hex === descriptor; 28 | }) ?? { 29 | code: descriptor, 30 | hex: "Unknown", 31 | }; 32 | 33 | return { 34 | keyDef, 35 | consumedLength, 36 | }; 37 | } 38 | 39 | function readNextDescriptor(text: string) { 40 | let pos = 0; 41 | const startBracket = 42 | text[pos]! in bracketDict ? (text[pos] as keyof typeof bracketDict) : ""; 43 | 44 | pos += startBracket.length; 45 | 46 | // `foo[[bar` is an escaped char at position 3, 47 | // but `foo[[[>5}bar` should be treated as `{` pressed down for 5 keydowns. 48 | const startBracketRepeated = startBracket 49 | ? (text.match(new RegExp(`^\\${startBracket}+`)) as RegExpMatchArray)[0] 50 | .length 51 | : 0; 52 | const isEscapedChar = startBracketRepeated === 2; 53 | 54 | const type = isEscapedChar ? "" : startBracket; 55 | 56 | return { 57 | type, 58 | ...(type === "" ? readPrintableChar(text, pos) : readTag(text, pos, type)), 59 | }; 60 | } 61 | 62 | function readPrintableChar(text: string, pos: number) { 63 | const descriptor = text[pos]; 64 | 65 | assertDescriptor(descriptor, text, pos); 66 | 67 | pos += descriptor.length; 68 | 69 | return { 70 | consumedLength: pos, 71 | descriptor, 72 | releasePrevious: false, 73 | releaseSelf: true, 74 | repeat: 1, 75 | }; 76 | } 77 | 78 | function readTag( 79 | text: string, 80 | pos: number, 81 | startBracket: keyof typeof bracketDict, 82 | ) { 83 | const descriptor = text.slice(pos).match(/^\w+/)?.[0]; 84 | 85 | assertDescriptor(descriptor, text, pos); 86 | 87 | pos += descriptor.length; 88 | 89 | const expectedEndBracket = bracketDict[startBracket]; 90 | const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : ""; 91 | 92 | if (!endBracket) { 93 | throw new Error( 94 | getErrorMessage(`"${expectedEndBracket}"`, text[pos], text), 95 | ); 96 | } 97 | 98 | pos += endBracket.length; 99 | 100 | return { 101 | consumedLength: pos, 102 | descriptor, 103 | }; 104 | } 105 | 106 | function assertDescriptor( 107 | descriptor: string | undefined, 108 | text: string, 109 | pos: number, 110 | ): asserts descriptor is string { 111 | if (!descriptor) { 112 | throw new Error(getErrorMessage("key descriptor", text[pos], text)); 113 | } 114 | } 115 | 116 | function getErrorMessage( 117 | expected: string, 118 | found: string | undefined, 119 | text: string, 120 | ) { 121 | return `Expected ${expected} but found "${found ?? ""}" in "${text}" 122 | See https://github.com/testing-library/user-event/blob/main/README.md#keyboardtext-options 123 | for more information about how userEvent parses your input.`; 124 | } 125 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/keyboard/index.ts: -------------------------------------------------------------------------------- 1 | import { keyboardImplementation } from "./keyboardImplementation"; 2 | import { defaultKeyMap } from "./keyMap"; 3 | import type { TestInstance } from "../../types"; 4 | import type { keyboardKey, keyboardOptions } from "./types"; 5 | 6 | export type { keyboardOptions, keyboardKey }; 7 | 8 | export function keyboard( 9 | instance: TestInstance, 10 | text: string, 11 | options?: Partial, 12 | ): void | Promise { 13 | const { promise } = keyboardImplementationWrapper(instance, text, options); 14 | 15 | if ((options?.delay ?? 0) > 0) { 16 | return promise; 17 | } else { 18 | // prevent users from dealing with UnhandledPromiseRejectionWarning in sync call 19 | promise.catch(console.error); 20 | } 21 | } 22 | 23 | export function keyboardImplementationWrapper( 24 | instance: TestInstance, 25 | text: string, 26 | config: Partial = {}, 27 | ): { 28 | promise: Promise; 29 | } { 30 | const { delay = 0, keyboardMap = defaultKeyMap } = config; 31 | const options = { 32 | delay, 33 | keyboardMap, 34 | }; 35 | 36 | return { 37 | promise: keyboardImplementation(instance, text, options), 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/keyboard/keyMap.ts: -------------------------------------------------------------------------------- 1 | import type { keyboardKey } from "./types"; 2 | 3 | /** 4 | * Mapping for a default US-104-QWERTY keyboard 5 | * 6 | * These use ANSI-C quoting, which seems to work for Linux, macOS, and Windows alike 7 | * @see https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#ANSI_002dC-Quoting 8 | * @see https://stackoverflow.com/questions/35429671/detecting-key-press-within-bash-scripts 9 | * @see https://gist.github.com/crutchcorn/2811db78a7b924cf54f4507198427fd2 10 | */ 11 | export const defaultKeyMap: Array = [ 12 | // alphanumeric keys 13 | { code: "Digit!", hex: "\x21" }, 14 | { code: "Digit#", hex: "\x23" }, 15 | { code: "Digit$", hex: "\x24" }, 16 | { code: "Digit%", hex: "\x25" }, 17 | { code: "Digit&", hex: "\x26" }, 18 | { code: "Digit(", hex: "\x29" }, 19 | { code: "Digit)", hex: "\x29" }, 20 | { code: "Digit*", hex: "\x2a" }, 21 | { code: "Digit-", hex: "\x2d" }, 22 | { code: "Digit@", hex: "\x40" }, 23 | { code: "Digit^", hex: "\x5e" }, 24 | { code: "Digit{", hex: "\x7b" }, 25 | { code: "Digit|", hex: "\x7c" }, 26 | { code: "Digit}", hex: "\x7d" }, 27 | { code: "Digit~", hex: "\x7e" }, 28 | { code: "Digit0", hex: "\x30" }, 29 | { code: "Digit1", hex: "\x31" }, 30 | { code: "Digit2", hex: "\x32" }, 31 | { code: "Digit3", hex: "\x33" }, 32 | { code: "Digit4", hex: "\x34" }, 33 | { code: "Digit5", hex: "\x35" }, 34 | { code: "Digit6", hex: "\x36" }, 35 | { code: "Digit7", hex: "\x37" }, 36 | { code: "Digit8", hex: "\x38" }, 37 | { code: "Digit9", hex: "\x39" }, 38 | { code: "KeyA", hex: "\x41" }, 39 | { code: "KeyB", hex: "\x42" }, 40 | { code: "KeyC", hex: "\x43" }, 41 | { code: "KeyD", hex: "\x44" }, 42 | { code: "KeyE", hex: "\x45" }, 43 | { code: "KeyF", hex: "\x46" }, 44 | { code: "KeyG", hex: "\x47" }, 45 | { code: "KeyH", hex: "\x48" }, 46 | { code: "KeyI", hex: "\x49" }, 47 | { code: "KeyJ", hex: "\x4a" }, 48 | { code: "KeyK", hex: "\x4b" }, 49 | { code: "KeyL", hex: "\x4c" }, 50 | { code: "KeyM", hex: "\x4d" }, 51 | { code: "KeyN", hex: "\x4e" }, 52 | { code: "KeyO", hex: "\x4f" }, 53 | { code: "KeyP", hex: "\x50" }, 54 | { code: "KeyQ", hex: "\x51" }, 55 | { code: "KeyR", hex: "\x52" }, 56 | { code: "KeyS", hex: "\x53" }, 57 | { code: "KeyT", hex: "\x54" }, 58 | { code: "KeyU", hex: "\x55" }, 59 | { code: "KeyV", hex: "\x56" }, 60 | { code: "KeyW", hex: "\x57" }, 61 | { code: "KeyX", hex: "\x58" }, 62 | { code: "KeyY", hex: "\x59" }, 63 | { code: "KeyZ", hex: "\x5a" }, 64 | { code: "Digit_", hex: "\x5f" }, 65 | { code: "KeyLowerA", hex: "\x61" }, 66 | { code: "KeyLowerB", hex: "\x62" }, 67 | { code: "KeyLowerC", hex: "\x63" }, 68 | { code: "KeyLowerD", hex: "\x64" }, 69 | { code: "KeyLowerE", hex: "\x65" }, 70 | { code: "KeyLowerF", hex: "\x66" }, 71 | { code: "KeyLowerG", hex: "\x67" }, 72 | { code: "KeyLowerH", hex: "\x68" }, 73 | { code: "KeyLowerI", hex: "\x69" }, 74 | { code: "KeyLowerJ", hex: "\x6a" }, 75 | { code: "KeyLowerK", hex: "\x6b" }, 76 | { code: "KeyLowerL", hex: "\x6c" }, 77 | { code: "KeyLowerM", hex: "\x6d" }, 78 | { code: "KeyLowerN", hex: "\x6e" }, 79 | { code: "KeyLowerO", hex: "\x6f" }, 80 | { code: "KeyLowerP", hex: "\x70" }, 81 | { code: "KeyLowerQ", hex: "\x71" }, 82 | { code: "KeyLowerR", hex: "\x72" }, 83 | { code: "KeyLowerS", hex: "\x73" }, 84 | { code: "KeyLowerT", hex: "\x74" }, 85 | { code: "KeyLowerU", hex: "\x75" }, 86 | { code: "KeyLowerV", hex: "\x76" }, 87 | { code: "KeyLowerW", hex: "\x77" }, 88 | { code: "KeyLowerX", hex: "\x78" }, 89 | { code: "KeyLowerY", hex: "\x79" }, 90 | { code: "KeyLowerZ", hex: "\x7a" }, 91 | 92 | // alphanumeric block - functional 93 | { code: "Space", hex: "\x20" }, 94 | { code: "Backspace", hex: "\x08" }, 95 | { code: "Enter", hex: "\x0D" }, 96 | 97 | // function 98 | { code: "Escape", hex: "\x1b" }, 99 | 100 | // arrows 101 | { code: "ArrowUp", hex: "\x1b\x5b\x41" }, 102 | { code: "ArrowDown", hex: "\x1B\x5B\x42" }, 103 | { code: "ArrowLeft", hex: "\x1b\x5b\x44" }, 104 | { code: "ArrowRight", hex: "\x1b\x5b\x43" }, 105 | 106 | // control pad 107 | { code: "Home", hex: "\x1b\x4f\x48" }, 108 | { code: "End", hex: "\x1b\x4f\x46" }, 109 | { code: "Delete", hex: "\x1b\x5b\x33\x7e" }, 110 | { code: "PageUp", hex: "\x1b\x5b\x35\x7e" }, 111 | { code: "PageDown", hex: "\x1b\x5b\x36\x7e" }, 112 | 113 | // TODO: add mappings 114 | ]; 115 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/keyboard/keyboardImplementation.ts: -------------------------------------------------------------------------------- 1 | import { fireEvent } from "../../events"; 2 | import { wait } from "../utils"; 3 | import { getNextKeyDef } from "./getNextKeyDef"; 4 | import type { TestInstance } from "../../types"; 5 | import type { keyboardKey, keyboardOptions } from "./types"; 6 | 7 | export async function keyboardImplementation( 8 | instance: TestInstance, 9 | text: string, 10 | options: keyboardOptions, 11 | ): Promise { 12 | const { keyDef, consumedLength } = getNextKeyDef(text, options); 13 | 14 | keypress(keyDef, instance); 15 | 16 | if (text.length > consumedLength) { 17 | if (options.delay > 0) { 18 | await wait(options.delay); 19 | } 20 | 21 | return keyboardImplementation( 22 | instance, 23 | text.slice(consumedLength), 24 | options, 25 | ); 26 | } 27 | return void undefined; 28 | } 29 | 30 | function keypress(keyDef: keyboardKey, instance: TestInstance) { 31 | fireEvent.write(instance, { value: keyDef.hex! }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/keyboard/types.ts: -------------------------------------------------------------------------------- 1 | export type keyboardOptions = { 2 | /** Delay between keystrokes */ 3 | delay: number; 4 | /** Keyboard layout to use */ 5 | keyboardMap: Array; 6 | }; 7 | 8 | export interface keyboardKey { 9 | /** Physical location on a keyboard */ 10 | code?: string; 11 | /** Character or functional key hex code */ 12 | hex?: string; 13 | } 14 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/user-event/utils.ts: -------------------------------------------------------------------------------- 1 | export function wait(time?: number) { 2 | return new Promise((resolve) => setTimeout(() => resolve(), time)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/cli-testing-library/src/vitest.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | 3 | import { expect } from "vitest"; 4 | import * as extensions from "./matchers/index"; 5 | import type { CLITestingLibraryMatchers } from "./matchers/types"; 6 | 7 | expect.extend(extensions); 8 | 9 | declare module "vitest" { 10 | interface Assertion extends CLITestingLibraryMatchers {} 11 | 12 | interface AsymmetricMatchersContaining 13 | extends CLITestingLibraryMatchers {} 14 | } 15 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/events.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { afterEach, expect, test } from "vitest"; 3 | import { cleanup, render } from "../src/pure"; 4 | import { fireEvent } from "../src/events"; 5 | import { waitFor } from "../src/wait-for"; 6 | import userEvent from "../src/user-event"; 7 | 8 | afterEach(async () => { 9 | await cleanup(); 10 | }); 11 | 12 | test("fireEvent write works", async () => { 13 | const props = await render("node", [ 14 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 15 | ]); 16 | 17 | const { clear, findByText } = props; 18 | 19 | const instance = await findByText("First option"); 20 | 21 | expect(instance).toBeTruthy(); 22 | 23 | // Windows uses ">", Linux/MacOS use "❯" 24 | expect(await findByText(/[❯>] One/)).toBeTruthy(); 25 | 26 | clear(); 27 | 28 | const down = "\x1B\x5B\x42"; 29 | fireEvent(instance, "write", { value: down }); 30 | 31 | expect(await findByText(/[❯>] Two/)).toBeTruthy(); 32 | 33 | clear(); 34 | 35 | const enter = "\x0D"; 36 | fireEvent(instance, "write", { value: enter }); 37 | 38 | expect(await findByText("First option: Two")).toBeTruthy(); 39 | }); 40 | 41 | test("FireEvent SigTerm works", async () => { 42 | const { findByText } = await render("node", [ 43 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 44 | ]); 45 | 46 | const instance = await findByText("First option"); 47 | 48 | expect(instance).toBeTruthy(); 49 | 50 | await fireEvent.sigterm(instance); 51 | 52 | await waitFor(() => expect(instance.hasExit()).toBeTruthy()); 53 | }); 54 | 55 | test("FireEvent SigKill works", async () => { 56 | const { findByText } = await render("node", [ 57 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 58 | ]); 59 | 60 | const instance = await findByText("First option"); 61 | 62 | expect(instance).toBeTruthy(); 63 | 64 | await fireEvent.sigkill(instance); 65 | 66 | await waitFor(() => expect(instance.hasExit()).toBeTruthy()); 67 | }); 68 | 69 | test("userEvent basic keyboard works", async () => { 70 | const { findByText } = await render("node", [ 71 | resolve(__dirname, "./execute-scripts/stdio-inquirer-input.js"), 72 | ]); 73 | 74 | const instance = await findByText("What is your name?"); 75 | expect(instance).toBeTruthy(); 76 | 77 | userEvent.keyboard(instance, "Test"); 78 | 79 | expect(await findByText("Test")).toBeTruthy(); 80 | }); 81 | 82 | test("userEvent basic keyboard works when bound", async () => { 83 | const { findByText, userEvent: userEventLocal } = await render("node", [ 84 | resolve(__dirname, "./execute-scripts/stdio-inquirer-input.js"), 85 | ]); 86 | 87 | const instance = await findByText("What is your name?"); 88 | expect(instance).toBeTruthy(); 89 | 90 | userEventLocal.keyboard("Test"); 91 | 92 | expect(await findByText("Test")).toBeTruthy(); 93 | }); 94 | 95 | test("UserEvent.keyboard enter key works", async () => { 96 | const props = await render("node", [ 97 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 98 | ]); 99 | 100 | const { clear, findByText, userEvent: userEventLocal } = props; 101 | 102 | const instance = await findByText("First option"); 103 | 104 | expect(instance).toBeTruthy(); 105 | 106 | // Windows uses ">", Linux/MacOS use "❯" 107 | expect(await findByText(/[❯>] One/)).toBeTruthy(); 108 | 109 | clear(); 110 | 111 | userEventLocal.keyboard("[ArrowDown]"); 112 | 113 | expect(await findByText(/[❯>] Two/)).toBeTruthy(); 114 | 115 | clear(); 116 | 117 | userEventLocal.keyboard("[Enter]"); 118 | 119 | expect(await findByText("First option: Two")).toBeTruthy(); 120 | }); 121 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/list-args.js: -------------------------------------------------------------------------------- 1 | console.log(process.argv); 2 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/log-err.js: -------------------------------------------------------------------------------- 1 | console.log("Log here"); 2 | console.warn("Warn here"); 3 | console.error("Error here"); 4 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/log-output.js: -------------------------------------------------------------------------------- 1 | import pc from "picocolors"; 2 | 3 | console.log("__disable_ansi_serialization"); 4 | 5 | console.log(pc.blue("Hello") + " World" + pc.red("!")); 6 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/stdio-inquirer-input.js: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | 3 | inquirer.prompt([ 4 | { 5 | type: "input", 6 | name: "name", 7 | message: "What is your name?", 8 | }, 9 | ]); 10 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/stdio-inquirer.js: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | 3 | inquirer.prompt([ 4 | { 5 | type: "list", 6 | name: "value", 7 | message: "First option:", 8 | choices: ["One", "Two", "Three"], 9 | }, 10 | ]); 11 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/execute-scripts/throw.js: -------------------------------------------------------------------------------- 1 | throw new Error("Search for this error in stderr"); 2 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/get-user-code-frame.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { afterEach, beforeEach, expect, test, vi } from "vitest"; 3 | import { getUserCodeFrame } from "../src/get-user-code-frame"; 4 | 5 | vi.mock(import("node:fs"), async (importOriginal) => { 6 | const actual = await importOriginal(); 7 | return { 8 | ...actual, 9 | default: { 10 | ...actual.default, 11 | readFileSync: vi.fn( 12 | () => ` 13 | import {screen} from '@testing-library/dom' 14 | it('renders', () => { 15 | document.body.appendChild( 16 | document.createTextNode('Hello world') 17 | ) 18 | screen.debug() 19 | expect(screen.getByText('Hello world')).toBeInTheDocument() 20 | }) 21 | `, 22 | ), 23 | }, 24 | } as never; 25 | }); 26 | 27 | const userStackFrame = 28 | "at somethingWrong (/sample-error/error-example.js:7:14)"; 29 | 30 | let globalErrorMock!: ReturnType; 31 | 32 | beforeEach(() => { 33 | globalErrorMock = vi.spyOn(global, "Error") as never; 34 | }); 35 | 36 | afterEach(() => { 37 | vi.mocked(global.Error).mockRestore(); 38 | }); 39 | 40 | test("it returns only user code frame when code frames from node_modules are first", () => { 41 | const stack = `Error: Kaboom 42 | at Object. (/sample-error/node_modules/@es2050/console/build/index.js:4:10) 43 | ${userStackFrame} 44 | `; 45 | globalErrorMock.mockImplementationOnce(() => ({ stack })); 46 | const userTrace = getUserCodeFrame(); 47 | 48 | expect(userTrace).toMatchInlineSnapshot(` 49 | "/sample-error/error-example.js:7:14 50 | 5 | document.createTextNode('Hello world') 51 | 6 | ) 52 | > 7 | screen.debug() 53 | | ^ 54 | " 55 | `); 56 | }); 57 | 58 | test("it returns only user code frame when node code frames are present afterwards", () => { 59 | const stack = `Error: Kaboom 60 | at Object. (/sample-error/node_modules/@es2050/console/build/index.js:4:10) 61 | ${userStackFrame} 62 | at Object. (/sample-error/error-example.js:14:1) 63 | at internal/main/run_main_module.js:17:47 64 | `; 65 | globalErrorMock.mockImplementationOnce(() => ({ stack })); 66 | const userTrace = getUserCodeFrame(); 67 | 68 | expect(userTrace).toMatchInlineSnapshot(` 69 | "/sample-error/error-example.js:7:14 70 | 5 | document.createTextNode('Hello world') 71 | 6 | ) 72 | > 7 | screen.debug() 73 | | ^ 74 | " 75 | `); 76 | }); 77 | 78 | test("it returns empty string if file from code frame can't be read", () => { 79 | (fs.readFileSync as ReturnType).mockImplementationOnce(() => { 80 | throw Error(); 81 | }); 82 | const stack = `Error: Kaboom 83 | ${userStackFrame} 84 | `; 85 | globalErrorMock.mockImplementationOnce(() => ({ stack })); 86 | 87 | expect(getUserCodeFrame()).toEqual(""); 88 | }); 89 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/matchers.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { expect, test } from "vitest"; 3 | import { render } from "../src/pure"; 4 | 5 | test("toBeInTheConsole should pass when something is in console", async () => { 6 | const { findByText } = await render("node", [ 7 | resolve(__dirname, "./execute-scripts/list-args.js"), 8 | "--version", 9 | ]); 10 | 11 | await expect( 12 | (async () => expect(await findByText("--version")).toBeInTheConsole())(), 13 | ).resolves.not.toThrow(); 14 | }); 15 | 16 | test("toBeInTheConsole should fail when something is not console", async () => { 17 | const { queryByText } = await render("node", [ 18 | resolve(__dirname, "./execute-scripts/list-args.js"), 19 | "--version", 20 | ]); 21 | 22 | expect(() => expect(queryByText("NotHere")).toBeInTheConsole()).toThrow( 23 | /value must be a TestInstance/, 24 | ); 25 | }); 26 | 27 | test("not.toBeInTheConsole should pass something is not console", async () => { 28 | const { queryByText } = await render("node", [ 29 | resolve(__dirname, "./execute-scripts/list-args.js"), 30 | "--version", 31 | ]); 32 | 33 | expect(() => 34 | expect(queryByText("NotHere")).not.toBeInTheConsole(), 35 | ).not.toThrow(); 36 | }); 37 | 38 | test("not.toBeInTheConsole should fail something is console", async () => { 39 | const { queryByText } = await render("node", [ 40 | resolve(__dirname, "./execute-scripts/list-args.js"), 41 | "--version", 42 | ]); 43 | 44 | expect(() => expect(queryByText("--version")).not.toBeInTheConsole()).toThrow( 45 | /Expected not to find the instance in the console/, 46 | ); 47 | }); 48 | 49 | test("toHaveErrorMessage should pass during stderr when no string passed", async () => { 50 | const instance = await render("node", [ 51 | resolve(__dirname, "./execute-scripts/throw.js"), 52 | ]); 53 | 54 | await expect( 55 | (async () => expect(instance).toHaveErrorMessage())(), 56 | ).resolves.not.toThrow(); 57 | }); 58 | 59 | test("toHaveErrorMessage should pass during stderr when string passed", async () => { 60 | const instance = await render("node", [ 61 | resolve(__dirname, "./execute-scripts/throw.js"), 62 | ]); 63 | 64 | await expect( 65 | (async () => 66 | expect(instance).toHaveErrorMessage(/Search for this error in stderr/))(), 67 | ).resolves.not.toThrow(); 68 | }); 69 | 70 | test("toHaveErrorMessage should fail when something is not in stderr", async () => { 71 | const { findByText } = await render("node", [ 72 | resolve(__dirname, "./execute-scripts/list-args.js"), 73 | "--version", 74 | ]); 75 | 76 | const instance = await findByText("--version"); 77 | expect(() => expect(instance).toHaveErrorMessage("Error isn't here")).toThrow( 78 | /Expected the instance to have error message/, 79 | ); 80 | }); 81 | 82 | test("toHaveErrorMessage should fail when null is passed", async () => { 83 | const { queryByText } = await render("node", [ 84 | resolve(__dirname, "./execute-scripts/list-args.js"), 85 | "--version", 86 | ]); 87 | 88 | expect(() => expect(queryByText("NotHere")).toHaveErrorMessage()).toThrow( 89 | /value must be a TestInstance/, 90 | ); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/matches.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { fuzzyMatches, matches } from "../src/matches"; 3 | 4 | // unit tests for text match utils 5 | 6 | const node = null; 7 | const normalizer = (str: string) => str; 8 | 9 | test("matchers accept strings", () => { 10 | expect(matches("ABC", node, "ABC", normalizer)).toBe(true); 11 | expect(fuzzyMatches("ABC", node, "ABC", normalizer)).toBe(true); 12 | }); 13 | 14 | test("matchers accept regex", () => { 15 | expect(matches("ABC", node, /ABC/, normalizer)).toBe(true); 16 | expect(fuzzyMatches("ABC", node, /ABC/, normalizer)).toBe(true); 17 | }); 18 | 19 | test("matchers accept functions", () => { 20 | expect( 21 | matches("ABC", node, (text: string) => text === "ABC", normalizer), 22 | ).toBe(true); 23 | expect( 24 | fuzzyMatches("ABC", node, (text: string) => text === "ABC", normalizer), 25 | ).toBe(true); 26 | }); 27 | 28 | test("matchers return false if text to match is not a string", () => { 29 | expect(matches(null as never, node, "ABC", normalizer)).toBe(false); 30 | expect(fuzzyMatches(null as never, node, "ABC", normalizer)).toBe(false); 31 | }); 32 | 33 | test("matchers throw on invalid matcher inputs", () => { 34 | expect(() => 35 | matches("ABC", node, null as never, normalizer), 36 | ).toThrowErrorMatchingInlineSnapshot( 37 | `[Error: It looks like null was passed instead of a matcher. Did you do something like getByText(null)?]`, 38 | ); 39 | expect(() => 40 | fuzzyMatches("ABC", node, undefined as never, normalizer), 41 | ).toThrowErrorMatchingInlineSnapshot( 42 | `[Error: It looks like undefined was passed instead of a matcher. Did you do something like getByText(undefined)?]`, 43 | ); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/pretty-cli.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { expect, test } from "vitest"; 3 | import { render } from "../src/pure"; 4 | import { prettyCLI } from "../src/pretty-cli"; 5 | 6 | test("Should pretty print with ANSI codes properly", async () => { 7 | const instance = await render("node", [ 8 | resolve(__dirname, "./execute-scripts/log-output.js"), 9 | ]); 10 | 11 | await instance.findByText("Hello"); 12 | 13 | expect(prettyCLI(instance, 9000)).toMatchInlineSnapshot(` 14 | "__disable_ansi_serialization 15 | Hello World!" 16 | `); 17 | }); 18 | 19 | test("Should escape ANSI codes properly when sliced too thin", async () => { 20 | const instance = await render("node", [ 21 | resolve(__dirname, "./execute-scripts/log-output.js"), 22 | ]); 23 | 24 | await instance.findByText("Hello"); 25 | 26 | expect(prettyCLI(instance, 30)).toMatchInlineSnapshot(` 27 | "__disable_ansi_serialization 28 | H" 29 | `); 30 | }); 31 | 32 | test("Should show proper stderr and stdout output", async () => { 33 | const instance = await render("node", [ 34 | resolve(__dirname, "./execute-scripts/log-err.js"), 35 | ]); 36 | 37 | await instance.findByError("Error here"); 38 | 39 | expect(prettyCLI(instance, 300)).toMatchInlineSnapshot(` 40 | "Log here 41 | Warn here 42 | Error here" 43 | `); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/queries.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { expect, test } from "vitest"; 3 | import { render } from "../src/pure"; 4 | import { waitFor } from "../src/wait-for"; 5 | 6 | test("findByError should show stderr", async () => { 7 | const { findByError } = await render("node", [ 8 | resolve(__dirname, "./execute-scripts/throw.js"), 9 | ]); 10 | expect(findByError("Search for this error in stderr")).toBeTruthy(); 11 | }); 12 | 13 | test("findByText should find stdout", async () => { 14 | const { findByText } = await render("node", [ 15 | resolve(__dirname, "./execute-scripts/list-args.js"), 16 | "--version", 17 | ]); 18 | 19 | expect(await findByText("--version")).toBeTruthy(); 20 | }); 21 | 22 | test("findByText should throw errors", async () => { 23 | const { findByText } = await render("node", [ 24 | resolve(__dirname, "./execute-scripts/list-args.js"), 25 | "--version", 26 | ]); 27 | 28 | await expect(() => findByText("--nothing")).rejects.toThrow( 29 | "Unable to find an stdout line with the text:", 30 | ); 31 | }); 32 | 33 | test("queryByText should find text", async () => { 34 | const { queryByText } = await render("node", [ 35 | resolve(__dirname, "./execute-scripts/list-args.js"), 36 | "--version", 37 | ]); 38 | 39 | expect(queryByText("--version")).toBeTruthy(); 40 | }); 41 | 42 | test("queryByText should not throw errors", async () => { 43 | const { queryByText } = await render("node", [ 44 | resolve(__dirname, "./execute-scripts/list-args.js"), 45 | "--version", 46 | ]); 47 | 48 | expect(await queryByText("--nothing")).toBeFalsy(); 49 | }); 50 | 51 | test("getByText should find text", async () => { 52 | const { getByText } = await render("node", [ 53 | resolve(__dirname, "./execute-scripts/list-args.js"), 54 | "--version", 55 | ]); 56 | 57 | expect(await waitFor(() => getByText("--version"))).toBeTruthy(); 58 | }); 59 | 60 | test("getByText should throw errors", async () => { 61 | const { getByText } = await render("node", [ 62 | resolve(__dirname, "./execute-scripts/list-args.js"), 63 | "--version", 64 | ]); 65 | 66 | await expect(() => waitFor(() => getByText("--nothing"))).rejects.toThrow( 67 | "Unable to find an stdout line with the text:", 68 | ); 69 | }); 70 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/render-basics.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { expect, test } from "vitest"; 3 | import { render } from "../src/pure"; 4 | import { waitFor } from "../src/wait-for"; 5 | 6 | test("Should expect error codes when intended", async () => { 7 | const instance = await render("node", [ 8 | resolve(__dirname, "./execute-scripts/throw.js"), 9 | ]); 10 | await waitFor(() => 11 | expect(instance.hasExit()).toMatchObject({ exitCode: 1 }), 12 | ); 13 | }); 14 | 15 | test("Should handle argument passing", async () => { 16 | const { findByText } = await render("node", [ 17 | resolve(__dirname, "./execute-scripts/list-args.js"), 18 | "--version", 19 | ]); 20 | 21 | expect(await findByText("--version")).toBeTruthy(); 22 | }); 23 | 24 | test("Is able to make terminal input and view in-progress stdout", async () => { 25 | const props = await render("node", [ 26 | resolve(__dirname, "./execute-scripts/stdio-inquirer.js"), 27 | ]); 28 | 29 | const { clear, findByText, userEvent } = props; 30 | 31 | const instance = await findByText("First option"); 32 | 33 | expect(instance).toBeTruthy(); 34 | 35 | // Windows uses ">", Linux/MacOS use "❯" 36 | expect(await findByText(/[❯>] One/)).toBeTruthy(); 37 | 38 | clear(); 39 | 40 | userEvent.keyboard("[ArrowDown]"); 41 | 42 | expect(await findByText(/[❯>] Two/)).toBeTruthy(); 43 | 44 | clear(); 45 | 46 | userEvent.keyboard("[Enter]"); 47 | 48 | expect(await findByText("First option: Two")).toBeTruthy(); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import "../src/vitest"; 2 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tests/user-keyboard.spec.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { expect, test } from "vitest"; 3 | import { render } from "../src/pure"; 4 | import { fireEvent } from "../src/events"; 5 | 6 | test("Should render { and } in user keyboard", async () => { 7 | const { findByText, userEvent: userEventLocal } = await render("node", [ 8 | resolve(__dirname, "./execute-scripts/stdio-inquirer-input.js"), 9 | ]); 10 | 11 | const instance = await findByText("What is your name?"); 12 | expect(instance).toBeTruthy(); 13 | 14 | userEventLocal.keyboard("{Hello}"); 15 | 16 | expect(await findByText("{Hello}")).toBeTruthy(); 17 | 18 | await fireEvent.sigterm(instance); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tsconfig.docs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/cli-testing-library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "Bundler" 5 | }, 6 | "include": ["src", "tests", "eslint.config.js", "vite.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/cli-testing-library/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, mergeConfig } from "vitest/config"; 2 | import { tanstackViteConfig } from "@tanstack/config/vite"; 3 | import packageJson from "./package.json"; 4 | 5 | const config = defineConfig({ 6 | test: { 7 | name: packageJson.name, 8 | dir: "./tests", 9 | watch: false, 10 | globals: true, 11 | setupFiles: ["./tests/setup.ts"], 12 | coverage: { enabled: true, provider: "istanbul", include: ["src/**/*"] }, 13 | typecheck: { enabled: true }, 14 | }, 15 | }); 16 | 17 | export default mergeConfig( 18 | config, 19 | tanstackViteConfig({ 20 | entry: [ 21 | "./src/index.ts", 22 | "./src/vitest.ts", 23 | "./src/jest-globals.ts", 24 | "./src/jest.ts", 25 | ], 26 | srcDir: "./src", 27 | }), 28 | ); 29 | -------------------------------------------------------------------------------- /packages/cli-testing-library/vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-testing-library-vitest", 3 | "version": "3.0.0", 4 | "description": "", 5 | "type": "module", 6 | "module": "./../dist/esm/vitest.js", 7 | "main": "./../dist/cjs/vitest.cjs", 8 | "types": "./../dist/cjs/vitest.d.cts", 9 | "exports": { 10 | "./package.json": "./package.json", 11 | ".": { 12 | "import": { 13 | "types": "./../dist/esm/vitest.d.ts", 14 | "default": "./../dist/esm/vitest.js" 15 | }, 16 | "require": { 17 | "types": "./../dist/cjs/vitest.d.cts", 18 | "default": "./../dist/cjs/vitest.cjs" 19 | } 20 | } 21 | }, 22 | "author": "Corbin Crutchley (https://crutchcorn.dev)", 23 | "license": "MIT", 24 | "sideEffects": true 25 | } 26 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "website" 4 | -------------------------------------------------------------------------------- /scripts/generateDocs.js: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import { generateReferenceDocs } from "@tanstack/config/typedoc"; 4 | 5 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 6 | 7 | /** @type {import('@tanstack/config/typedoc').Package[]} */ 8 | const packages = [ 9 | { 10 | name: "cli-testing-library", 11 | entryPoints: [ 12 | resolve(__dirname, "../packages/cli-testing-library/src/index.ts"), 13 | ], 14 | tsconfig: resolve( 15 | __dirname, 16 | "../packages/cli-testing-library/tsconfig.docs.json", 17 | ), 18 | outputDir: resolve(__dirname, "../docs/reference"), 19 | }, 20 | ]; 21 | 22 | await generateReferenceDocs({ packages }); 23 | 24 | process.exit(0); 25 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { resolve } from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import { publish } from "@tanstack/config/publish"; 6 | 7 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 8 | 9 | await publish({ 10 | packages: [ 11 | { 12 | name: "cli-testing-library", 13 | packageDir: "packages/cli-testing-library", 14 | }, 15 | ], 16 | branchConfigs: { 17 | main: { 18 | prerelease: false, 19 | }, 20 | alpha: { 21 | prerelease: true, 22 | }, 23 | beta: { 24 | prerelease: true, 25 | }, 26 | }, 27 | rootDir: resolve(__dirname, ".."), 28 | branch: process.env.BRANCH, 29 | tag: process.env.TAG, 30 | ghToken: process.env.GH_TOKEN, 31 | }); 32 | 33 | process.exit(0); 34 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "Bundler" 5 | }, 6 | "include": ["**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "allowUnreachableCode": false, 7 | "allowUnusedLabels": false, 8 | "checkJs": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "isolatedModules": true, 13 | "lib": ["ES2022"], 14 | "module": "ES2022", 15 | "moduleResolution": "Node", 16 | "noEmit": true, 17 | "noImplicitReturns": true, 18 | "noUncheckedIndexedAccess": true, 19 | "noUnusedLocals": false, // TODO enable 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "skipLibCheck": true, 23 | "strict": true, 24 | "target": "ES2020" 25 | }, 26 | "include": ["eslint.config.js", "prettier.config.js"] 27 | } 28 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /website/astro.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { defineConfig } from "astro/config"; 3 | import starlight from "@astrojs/starlight"; 4 | import react from "@astrojs/react"; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [ 9 | react(), 10 | starlight({ 11 | title: "CLI Testing Library", 12 | logo: { 13 | src: "./public/koala.png", 14 | alt: "A koala emoji", 15 | }, 16 | favicon: "./public/koala.png", 17 | social: { 18 | github: "https://github.com/crutchcorn/cli-testing-library", 19 | }, 20 | components: { 21 | Head: "./src/components/head.astro", 22 | }, 23 | customCss: ["./src/styles/global.css"], 24 | sidebar: [ 25 | { 26 | label: "Guides", 27 | items: [ 28 | { label: "Introduction", slug: "guides/introduction" }, 29 | { label: "API", slug: "guides/api" }, 30 | { label: "Configuration Options", slug: "guides/configure" }, 31 | { label: "Debug", slug: "guides/debug" }, 32 | { 33 | label: 34 | "Differences Between CLI Testing Library & Testing Library", 35 | slug: "guides/differences", 36 | }, 37 | { label: "Firing Events", slug: "guides/fire-event" }, 38 | { label: "Matchers", slug: "guides/matchers" }, 39 | { label: "About Queries", slug: "guides/queries" }, 40 | { label: "User Event", slug: "guides/user-event" }, 41 | ], 42 | }, 43 | ], 44 | }), 45 | ], 46 | }); 47 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "type": "module", 4 | "private": true, 5 | "version": "0.0.1", 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/react": "^4.1.2", 15 | "@astrojs/starlight": "^0.31.1", 16 | "astro": "^5.0.2", 17 | "react": "^19.0.0", 18 | "react-dom": "^19.0.0", 19 | "sharp": "^0.33.5" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^19.0.2", 23 | "@types/react-dom": "^19.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /website/public/koala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crutchcorn/cli-testing-library/ffa9691659ded91372dae522fde7c7a856330566/website/public/koala.png -------------------------------------------------------------------------------- /website/public/share-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crutchcorn/cli-testing-library/ffa9691659ded91372dae522fde7c7a856330566/website/public/share-banner.png -------------------------------------------------------------------------------- /website/src/components/head.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { Props } from "@astrojs/starlight/props"; 3 | import Default from "@astrojs/starlight/components/Head.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /website/src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection } from "astro:content"; 2 | import { docsLoader } from "@astrojs/starlight/loaders"; 3 | import { docsSchema } from "@astrojs/starlight/schema"; 4 | 5 | export const collections = { 6 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), 7 | }; 8 | -------------------------------------------------------------------------------- /website/src/content/docs/guides: -------------------------------------------------------------------------------- 1 | ../../../../docs/ -------------------------------------------------------------------------------- /website/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CLI Testing Library 🐨 Test CLI tools with the Testing Library API 3 | head: 4 | - tag: title 5 | content: CLI Testing Library 🐨 Test CLI tools with the Testing Library 6 | description: Simple and complete CLI testing utilities that encourage good testing practices. 7 | template: splash 8 | editUrl: false 9 | lastUpdated: false 10 | hero: 11 | title: Test CLI tools with the Testing Library API 12 | tagline: Simple and complete CLI testing utilities that encourage good testing practices. 13 | image: 14 | file: ../../../public/koala.png 15 | actions: 16 | - text: Get started 17 | icon: right-arrow 18 | link: /guides/introduction 19 | - text: View on GitHub 20 | icon: external 21 | variant: minimal 22 | link: https://github.com/crutchcorn/cli-testing-library 23 | --- 24 | 25 | import { CardGrid, Card } from '@astrojs/starlight/components'; 26 | 27 | 28 | 29 | The Testing Library family of libraries is well loved and thought out to make testing a joy. CLI Testing Library is no different. 30 | 31 | 32 | By running your tests as if they were run in a real terminal, you can see exactly what your users see and have confidence that your tests are correct. 33 | 34 | 35 | CLI Testing Library supports any testing framework. Use the one you know and love, yes, even that one. 36 | 37 | 38 | We love open source and are committed to making the best possible testing utilities for the community. 39 | 40 | 41 | -------------------------------------------------------------------------------- /website/src/styles/global.css: -------------------------------------------------------------------------------- 1 | /* Dark mode colors. */ 2 | :root { 3 | --sl-color-accent-low: #232427; 4 | --sl-color-accent: #666973; 5 | --sl-color-accent-high: #c7c8cc; 6 | --sl-color-white: #ffffff; 7 | --sl-color-gray-1: #eceef2; 8 | --sl-color-gray-2: #c6c8ce; 9 | --sl-color-gray-3: #a0a4ae; 10 | --sl-color-gray-4: #545861; 11 | --sl-color-gray-5: #353841; 12 | --sl-color-gray-6: #24272f; 13 | --sl-color-black: #17181c; 14 | } 15 | /* Light mode colors. */ 16 | :root[data-theme="light"] { 17 | --sl-color-accent-low: #d6d7d9; 18 | --sl-color-accent: #4b4e57; 19 | --sl-color-accent-high: #313236; 20 | --sl-color-white: #17181c; 21 | --sl-color-gray-1: #24272f; 22 | --sl-color-gray-2: #353841; 23 | --sl-color-gray-3: #545861; 24 | --sl-color-gray-4: #888b96; 25 | --sl-color-gray-5: #c0c2c7; 26 | --sl-color-gray-6: #eceef2; 27 | --sl-color-gray-7: #f5f6f8; 28 | --sl-color-black: #ffffff; 29 | } 30 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "include": [".astro/types.d.ts", "**/*"], 4 | "exclude": ["dist"] 5 | } 6 | --------------------------------------------------------------------------------