├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin
└── dts-jest-remap.js
├── fixtures
├── create-assertion-expression
│ └── example.ts
├── create-setup-expression
│ └── example.ts
├── create-snapshots
│ ├── example.ts
│ ├── no-assertion.ts
│ ├── ts-expect-error.ts
│ └── unmatched.ts
├── find-docblock-options
│ ├── disable-test-type.ts
│ ├── disable-test-value.ts
│ ├── enable-test-type.ts
│ ├── enable-test-value.ts
│ ├── multi-option-value.ts
│ ├── normal-comment.ts
│ ├── second-docblock.ts
│ └── unexpected.ts
├── find-trigger-bodies
│ ├── invalid-group.ts
│ ├── simple.ts
│ └── unattachable.ts
├── find-trigger-footers
│ ├── simple.ts
│ └── unattachable.ts
├── find-trigger-headers
│ ├── group.ts
│ ├── method.ts
│ ├── simple.ts
│ ├── ts-expect-error-equivalent.ts
│ ├── ts-expect-error.ts
│ └── unmatched.ts
├── find-triggers
│ └── example.ts
├── load-compiler-options
│ ├── example
│ │ ├── placeholder.ts
│ │ ├── tsconfig.base.json
│ │ ├── tsconfig.json
│ │ └── types
│ │ │ └── index.d.ts
│ └── invalid
│ │ ├── placeholder.ts
│ │ └── tsconfig.json
├── normalize-trigger-header-methods
│ ├── group-only.ts
│ ├── multi-group-only.ts
│ ├── multi-mixed-only.ts
│ ├── multi-trigger-only.ts
│ ├── no-only.ts
│ ├── trigger-only-in-group-only.ts
│ ├── trigger-only-in-group-skip.ts
│ └── trigger-only.ts
├── remap-cli
│ ├── __snapshots__
│ │ └── example.ts.snap
│ ├── empty.ts
│ ├── example.ts
│ └── remapped.ts
├── remap
│ ├── __snapshots__
│ │ ├── example.ts.snap
│ │ └── unmatched.ts.snap
│ ├── example.ts
│ └── unmatched.ts
├── runtime
│ └── example.ts
└── transform
│ ├── all.ts
│ ├── commonjs.ts
│ ├── multi-line.ts
│ └── no-footers.ts
├── jest.json
├── package.json
├── renovate.json
├── reporter.js
├── src
├── __snapshots__
│ ├── remap-cli.test.ts.snap
│ ├── remap.test.ts.snap
│ ├── reporter.test.ts.snap
│ ├── runtime.test.ts.snap
│ └── transform.test.ts.snap
├── definitions.ts
├── helpers
│ ├── cwd-serializer.ts
│ ├── load-fixture.ts
│ ├── trigger-header-serializer.ts
│ └── version-serializer.ts
├── index.test.ts
├── index.ts
├── remap-cli-parser.ts
├── remap-cli.test.ts
├── remap-cli.ts
├── remap.test.ts
├── remap.ts
├── reporter.test.ts
├── reporter.ts
├── runtime.test.ts
├── runtime.ts
├── setup.ts
├── transform.test.ts
├── transform.ts
└── utils
│ ├── __snapshots__
│ ├── apply-grouping.test.ts.snap
│ ├── create-assertion-expression.test.ts.snap
│ ├── create-message.test.ts.snap
│ ├── create-setup-expression.test.ts.snap
│ ├── create-snapshots.test.ts.snap
│ ├── find-docblock-options.test.ts.snap
│ ├── find-trigger-bodies.test.ts.snap
│ ├── find-trigger-footers.test.ts.snap
│ ├── find-trigger-headers.test.ts.snap
│ ├── find-triggers.test.ts.snap
│ ├── get-comment-content.test.ts.snap
│ ├── indent.test.ts.snap
│ ├── load-compiler-options.test.ts.snap
│ ├── normalize-expected-value.test.ts.snap
│ ├── normalize-trigger-header-methods.test.ts.snap
│ ├── parse-trigger-header-flags.test.ts.snap
│ └── traverse-node.test.ts.snap
│ ├── apply-grouping.test.ts
│ ├── apply-grouping.ts
│ ├── create-assertion-expression.test.ts
│ ├── create-assertion-expression.ts
│ ├── create-group-expression.ts
│ ├── create-message.test.ts
│ ├── create-message.ts
│ ├── create-setup-expression.test.ts
│ ├── create-setup-expression.ts
│ ├── create-snapshots.test.ts
│ ├── create-snapshots.ts
│ ├── create-source-file.ts
│ ├── create-test-expression.ts
│ ├── create-typescript-info.ts
│ ├── find-docblock-options.test.ts
│ ├── find-docblock-options.ts
│ ├── find-trigger-bodies.test.ts
│ ├── find-trigger-bodies.ts
│ ├── find-trigger-footers.test.ts
│ ├── find-trigger-footers.ts
│ ├── find-trigger-headers.test.ts
│ ├── find-trigger-headers.ts
│ ├── find-triggers.test.ts
│ ├── find-triggers.ts
│ ├── for-each-comment.ts
│ ├── get-comment-content.test.ts
│ ├── get-comment-content.ts
│ ├── get-description-for-jest.ts
│ ├── get-diagnostic-message.ts
│ ├── get-display-line.ts
│ ├── get-group-description.ts
│ ├── get-node-one-line-text.ts
│ ├── get-snapshot-description.ts
│ ├── get-trigger-groups.ts
│ ├── get-trigger-line.ts
│ ├── indent.test.ts
│ ├── indent.ts
│ ├── load-compiler-options.test.ts
│ ├── load-compiler-options.ts
│ ├── load-typescript.ts
│ ├── normalize-config.ts
│ ├── normalize-expected-value.test.ts
│ ├── normalize-expected-value.ts
│ ├── normalize-trigger-header-methods.test.ts
│ ├── normalize-trigger-header-methods.ts
│ ├── parse-trigger-header-flags.test.ts
│ ├── parse-trigger-header-flags.ts
│ ├── replace-cwd.ts
│ ├── traverse-node.test.ts
│ └── traverse-node.ts
├── tests
├── __snapshots__
│ └── example.ts.snap
├── example.snap.ts
├── example.ts
└── jest.json
├── transform.js
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.png binary
3 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | test:
9 | name: Test
10 | strategy:
11 | matrix:
12 | version: [12, lts/*]
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout Repository
16 | uses: actions/checkout@v3
17 |
18 | - name: Setup Node.js
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.version }}
22 | cache: 'yarn'
23 |
24 | - name: Install Dependencies
25 | run: yarn
26 |
27 | - name: Lint
28 | run: yarn run lint
29 |
30 | - name: Test
31 | run: yarn run test -- --ci --verbose --coverage
32 |
33 | - name: Test Integration
34 | run: yarn run test-integration -- --ci --verbose
35 |
36 | - name: Test Remap
37 | run: yarn run remap-integration -- --check --list-different
38 |
39 | - name: Upload Coverage
40 | uses: codecov/codecov-action@v2
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | lib/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikatyang/dts-jest/86823fe5959be5143842f93c65cc7fafc63bdd64/.npmignore
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'all',
4 | arrowParens: 'avoid',
5 | };
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ### [26.0.2](https://github.com/ikatyang/dts-jest/compare/v26.0.1...v26.0.2) (2024-02-25)
6 |
7 | * v26.0.1 has been unpublished due to missing files on npm, publish again to address the issue
8 |
9 | ### [26.0.1](https://github.com/ikatyang/dts-jest/compare/v26.0.0...v26.0.1) (2024-02-25)
10 |
11 |
12 | ### Bug Fixes
13 |
14 | * compatible with Jest 30 ([#416](https://github.com/ikatyang/dts-jest/issues/416)) ([9fec19f](https://github.com/ikatyang/dts-jest/commit/9fec19fe75bcc81d4351a2085fa769d77fea73fb))
15 |
16 | ## [26.0.0](https://github.com/ikatyang/dts-jest/compare/v25.0.0...v26.0.0) (2023-04-07)
17 |
18 |
19 | ### ⚠ BREAKING CHANGES
20 |
21 | * TypeScript 4 is now the minimal supported version
22 |
23 | ### Features
24 |
25 | * support TypeScript 5 ([0237ac6](https://github.com/ikatyang/dts-jest/commit/0237ac6df3e921d384be2222bea2165c5a74ab9e))
26 |
27 | ## [25.0.0](https://github.com/ikatyang/dts-jest/compare/v24.0.0...v25.0.0) (2022-05-14)
28 |
29 |
30 | ### Features
31 |
32 | * support Jest 28 ([4ccfe7a](https://github.com/ikatyang/dts-jest/commit/4ccfe7aed77b44379f0c78be75cb8a684dc30791))
33 |
34 |
35 | ### BREAKING CHANGES
36 |
37 | * Jest 28 is now the minimal supported version
38 |
39 |
40 |
41 |
42 | # [24.0.0](https://github.com/ikatyang/dts-jest/compare/v23.3.0...v24.0.0) (2021-05-30)
43 |
44 |
45 | ### Features
46 |
47 | * support Jest 27 ([867d3dd](https://github.com/ikatyang/dts-jest/commit/867d3dd))
48 |
49 |
50 | ### BREAKING CHANGES
51 |
52 | * Jest 27 is now the minimal supported version
53 |
54 |
55 |
56 |
57 | # [23.3.0](https://github.com/ikatyang/dts-jest/compare/v23.2.0...v23.3.0) (2020-08-12)
58 |
59 |
60 | ### Features
61 |
62 | * **deps:** upgrade yargs to 14.x ([#396](https://github.com/ikatyang/dts-jest/issues/396)) ([4584254](https://github.com/ikatyang/dts-jest/commit/4584254))
63 |
64 |
65 |
66 |
67 | # [23.2.0](https://github.com/ikatyang/dts-jest/compare/v23.1.0...v23.2.0) (2020-07-29)
68 |
69 |
70 | ### Features
71 |
72 | * add `:not-any` flag ([#394](https://github.com/ikatyang/dts-jest/issues/394)) ([7ecd983](https://github.com/ikatyang/dts-jest/commit/7ecd983))
73 |
74 |
75 |
76 |
77 | # [23.1.0](https://github.com/ikatyang/dts-jest/compare/v23.0.0...v23.1.0) (2020-06-27)
78 |
79 |
80 | ### Features
81 |
82 | * support `@ts-expect-error` ([60451a3](https://github.com/ikatyang/dts-jest/commit/60451a3))
83 |
84 |
85 |
86 |
87 | # [23.0.0](https://github.com/ikatyang/dts-jest/compare/v22.0.4...v23.0.0) (2019-04-13)
88 |
89 |
90 | ### Chores
91 |
92 | * update dependencies ([#324](https://github.com/ikatyang/dts-jest/issues/324)) ([937b81a](https://github.com/ikatyang/dts-jest/commit/937b81a))
93 |
94 |
95 | ### Features
96 |
97 | * support jest 22+ ([#325](https://github.com/ikatyang/dts-jest/issues/325)) ([4416e76](https://github.com/ikatyang/dts-jest/commit/4416e76))
98 |
99 |
100 | ### BREAKING CHANGES
101 |
102 | * drop support for jest < 22
103 | * drop support for node v4
104 |
105 |
106 |
107 |
108 | ## [22.0.4](https://github.com/ikatyang/dts-jest/compare/v22.0.3...v22.0.4) (2018-06-27)
109 |
110 |
111 | ### Bug Fixes
112 |
113 | * **deps:** update dependency yargs to ^9.0.0 ([#102](https://github.com/ikatyang/dts-jest/issues/102)) ([67be7f5](https://github.com/ikatyang/dts-jest/commit/67be7f5))
114 | * support `typeRoots` ([#252](https://github.com/ikatyang/dts-jest/issues/252)) ([b918aa8](https://github.com/ikatyang/dts-jest/commit/b918aa8))
115 |
116 |
117 |
118 |
119 | ## [22.0.3](https://github.com/ikatyang/dts-jest/compare/v22.0.2...v22.0.3) (2017-09-05)
120 |
121 |
122 | ### Bug Fixes
123 |
124 | * **deps:** update dependency pretty-format to ^21.0.0 ([#88](https://github.com/ikatyang/dts-jest/issues/88)) ([3721a48](https://github.com/ikatyang/dts-jest/commit/3721a48))
125 | * **peerDeps:** accept jest ^21.0.0 ([#89](https://github.com/ikatyang/dts-jest/issues/89)) ([fc47496](https://github.com/ikatyang/dts-jest/commit/fc47496))
126 |
127 |
128 |
129 |
130 | ## [22.0.2](https://github.com/ikatyang/dts-jest/compare/v22.0.1...v22.0.2) (2017-09-04)
131 |
132 |
133 | ### Bug Fixes
134 |
135 | * **options:** add `` placeholder for `typescript` option to better describe its path ([#86](https://github.com/ikatyang/dts-jest/issues/86)) ([a003a31](https://github.com/ikatyang/dts-jest/commit/a003a31))
136 |
137 |
138 |
139 |
140 | ## [22.0.1](https://github.com/ikatyang/dts-jest/compare/v22.0.0...v22.0.1) (2017-09-01)
141 |
142 |
143 | ### Bug Fixes
144 |
145 | * **runtime:** show 1-based line number ([#82](https://github.com/ikatyang/dts-jest/issues/82)) ([de4c6aa](https://github.com/ikatyang/dts-jest/commit/de4c6aa))
146 |
147 |
148 |
149 |
150 | # [22.0.0](https://github.com/ikatyang/dts-jest/compare/v21.0.0...v22.0.0) (2017-08-31)
151 |
152 |
153 | ### Bug Fixes
154 |
155 | * **deps:** jest peerDeps should allow ^20.0.0 ([1a24239](https://github.com/ikatyang/dts-jest/commit/1a24239))
156 | * report unmatched diagnostic ([#52](https://github.com/ikatyang/dts-jest/issues/52)) ([4ab0f86](https://github.com/ikatyang/dts-jest/commit/4ab0f86))
157 | * **deps:** update peerDeps typescript to ^2.3.0 ([c075dd2](https://github.com/ikatyang/dts-jest/commit/c075dd2))
158 |
159 | ### Features
160 |
161 | * add ability to specify which typescript to use ([#49](https://github.com/ikatyang/dts-jest/issues/49)) ([9213bc1](https://github.com/ikatyang/dts-jest/commit/9213bc1))
162 | * add reporter to show current TS version ([#51](https://github.com/ikatyang/dts-jest/issues/51)) ([bf4ee48](https://github.com/ikatyang/dts-jest/commit/bf4ee48))
163 | * combine type and value tests ([#69](https://github.com/ikatyang/dts-jest/issues/69)) ([876b37d](https://github.com/ikatyang/dts-jest/commit/876b37d))
164 | * redefine flags ([#54](https://github.com/ikatyang/dts-jest/issues/54)) ([dc1883f](https://github.com/ikatyang/dts-jest/commit/dc1883f))
165 | * rewrite remap & remap-cli ([#59](https://github.com/ikatyang/dts-jest/issues/59)) ([1db5ea0](https://github.com/ikatyang/dts-jest/commit/1db5ea0))
166 | * show detailed test title ([#74](https://github.com/ikatyang/dts-jest/issues/74)) ([2eac61f](https://github.com/ikatyang/dts-jest/commit/2eac61f))
167 | * support `tsconfig.json` literal options ([#56](https://github.com/ikatyang/dts-jest/issues/56)) ([f9dd34a](https://github.com/ikatyang/dts-jest/commit/f9dd34a))
168 |
169 |
170 | ### BREAKING CHANGES
171 |
172 | * **deps:** drop TS < v2.3
173 | * **transform-actual:** remove transformer `transform-actual` as it currently combined with `transform`
174 | * **remap:** [API] `remap(...)`
175 | * before
176 | * `snapshot_content`
177 | * allow `string` (raw content from *.snap)
178 | * allow `Record` (unparsed content from *.snap)
179 | * after
180 | * `snapshot_content`
181 | * allow `string` (raw content from *.snap)
182 | * allow `Record` (parsed content from *.snap)
183 | * **remap-cli:** rename bin from `dts-jest-remap-snapshot` to `dts-jest-remap`
184 | * input using source file instead of snapshot file, e.g.
185 | * before: `./__snapshots__/example.ts.snap`
186 | * after: `./example.ts`
187 | * output content does not print to stdout now, use `--outDir` and `--rename` to specify output path instead
188 | * **configs:** replace config value with config literal
189 | * Before
190 | ```json5
191 | {
192 | "target": 5 // ts.ScriptTarget.ESNext
193 | }
194 | ```
195 | * After
196 | ```json
197 | {
198 | "target": "esnext"
199 | }
200 | ```
201 | * **flags:** redefine flag
202 | * type tests
203 | * `@dts-jest` -> `@dts-jest:snapshot`
204 | * `@dts-jest:snap` -> `@dts-jest:snapshot`
205 | * `@dts-jest:pass` -> `@dts-jest:pass:snapshot`
206 | * `@dts-jest:fail` -> `@dts-jest:fail:snapshot`
207 | * actual tests
208 | * `@dts-jest` + `//=> value` -> `//=> :no-error`
209 | * `@dts-jest:snap` + `//=> value` -> `//=> :no-error`
210 | * `@dts-jest:show` + `//=> value` -> `//=> ?`
211 | * `@dts-jest:pass` + `//=> value` -> `//=> value`
212 | * `@dts-jest:fail` + `//=> value` -> `//=> :error`
213 |
214 |
215 | # [21.0.0](https://github.com/ikatyang/dts-jest/compare/v20.5.1...v21.0.0) (2017-08-18)
216 |
217 |
218 | ### Features
219 |
220 | * **deps:** move typescript to peerDependecies ([#38](https://github.com/ikatyang/dts-jest/issues/38)) ([e9800f1](https://github.com/ikatyang/dts-jest/commit/e9800f1))
221 |
222 |
223 | ### BREAKING CHANGES
224 |
225 | * **deps:** TypeScript now has to be installed manually so that you can choose which version to use
226 |
227 | * **version:** This project now **DOES NOT** use the same versioning as Jest
228 |
229 |
230 | ## v20.5.1 (2017-06-30)
231 |
232 | #### 🚀 New Feature
233 | - allow using snapshot-content object for `remap-snapshot`
234 | - allow specifying snapshot filename for `remap-snapshot` so as to handle cache
235 |
236 | ## v20.5.0 (2017-06-30)
237 |
238 | #### 🚀 New Feature
239 | - Add `remap-snapshot` to generate diff-friendly snapshots
240 |
241 | ## v20.4.1 (2017-06-24)
242 |
243 | #### 🐛 Bug Fix
244 | - Fix transpile error for actual test
245 |
246 | ## v20.4.0 (2017-06-24)
247 |
248 | #### 🚀 New Feature
249 | - Add actual test transformer (`dts-jest/transform-actual`) with `//=> value` comment
250 |
251 | ## v20.3.1 (2017-06-21)
252 |
253 | #### 🐛 Bug Fix
254 | - Fix indentation for description of grouped test
255 |
256 | ## v20.3.0 (2017-06-21)
257 |
258 | #### 🚀 New Feature
259 | - Add group flag to categorize test cases
260 | - Add default flags ( `:test`, `:shot` ) to show its explicit flag
261 | - Allow to set flags with any order, e.g. `:show:only`, `:only:show`
262 |
263 | #### 🐛 Bug Fix
264 | - Remove unnecessary leading spaces in expressions (dedent)
265 |
266 | ## v20.2.0 (2017-06-20)
267 |
268 | #### 🚀 New Feature
269 | - Add flags ( `:pass`, `:fail`, `:only:pass`, `:only:fail` ) to assert its result
270 |
271 | ## v20.1.0 (2017-06-13)
272 |
273 | #### 💥 Breaking Change
274 | - Use same MAJOR version as Jest
275 | - Remove server since tests should be separated
276 |
277 | #### 🚀 New Feature
278 | - Add config `type_format`
279 | - Display description in `:show`
280 |
281 | ## v20.0.6 (2017-06-10)
282 |
283 | #### 🐛 Bug Fix
284 | - Fix transforming for template token
285 |
286 | ## v20.0.4 (2017-06-09)
287 |
288 | #### 🐛 Bug Fix
289 | - Fix unexpected filenames
290 |
291 | #### 🏠 Internal
292 | - Use POST for modification actions
293 |
294 | ## v20.0.3 (2017-06-03)
295 |
296 | #### 💥 Breaking Change
297 | - setup a server for initializing TS source file at once
298 | - remove useless config `type_detail`, `type_format`, `snapshot_formatter`
299 |
300 | #### 🚀 New Feature
301 | - allow to use `` in config `tsconfig`
302 |
303 | ## v20.0.2 (2017-05-16)
304 |
305 | #### 🐛 Bug Fix
306 | - Fix missing config
307 |
308 | ## v20.0.1 (2017-05-16)
309 |
310 | #### 🚀 New Feature
311 | - detect unattachable triggers
312 | - allow to customize `:show` message with `reporter` option
313 | - allow to customize inferred type with `type_detail` and `type_format` option
314 | - allow to customize snapshot content with `snapshot_formatter` option
315 |
316 | #### 🏠 Internal
317 | - rewrite for better user experience about cache
318 |
319 | ## v20.0.0 (2017-05-14)
320 |
321 | #### 🚀 New Feature
322 | - Use same MAJOR.MINOR version as Jest
323 |
324 | #### 📝 Documentation
325 | - Fix image urls in README.md
326 |
327 | ## v1.0.5 (2017-05-13)
328 |
329 | #### 🐛 Bug Fix
330 | - Fix dependency
331 |
332 | ## v1.0.4 (2017-05-13)
333 |
334 | #### 🚀 New Feature
335 | - Release first version
336 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Ika (https://github.com/ikatyang)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dts-jest
2 |
3 | [](https://www.npmjs.com/package/dts-jest)
4 | [](https://travis-ci.org/ikatyang/dts-jest/builds)
5 | [](https://codecov.io/gh/ikatyang/dts-jest)
6 |
7 | A preprocessor for [Jest](https://facebook.github.io/jest/) to snapshot test [TypeScript declaration (.d.ts)](http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html) files
8 |
9 | [Changelog](https://github.com/ikatyang/dts-jest/blob/master/CHANGELOG.md)
10 |
11 | - [Install](#install)
12 | - [Usage](#usage)
13 | - [Writing Tests](#writing-tests)
14 | - [Patterns](#patterns)
15 | - [Patterns for Testing](#patterns-for-testing)
16 | - [Patterns for Grouping](#patterns-for-grouping)
17 | - [Patterns for File-Level Config](#patterns-for-file-level-config)
18 | - [Testing](#testing)
19 | - [Configs](#configs)
20 | - [Generate diff-friendly snapshots](#generate-diff-friendly-snapshots)
21 | - [Reporter](#reporter)
22 | - [FAQ](#faq)
23 | - [Development](#development)
24 | - [Related](#related)
25 | - [License](#license)
26 |
27 | ## Install
28 |
29 | ```sh
30 | # using npm
31 | npm install --save-dev dts-jest jest typescript
32 |
33 | # using yarn
34 | yarn add --dev dts-jest jest typescript
35 | ```
36 |
37 | - require `jest@>=28` and `typescript@>=4`
38 |
39 | | dts-jest | jest | typescript |
40 | | -------- | -------- | ---------- |
41 | | 26 | >=28 | >=4 |
42 | | 25 | >=28 <30 | >=2.3 <5 |
43 | | 24 | 27 | >=2.3 <5 |
44 | | 23 | >=22 <27 | >=2.3 <5 |
45 |
46 | ## Usage
47 |
48 | Modify your [Jest config](https://facebook.github.io/jest/docs/en/configuration.html) so that looks something like:
49 |
50 | (./package.json)
51 |
52 | ```json
53 | {
54 | "scripts": {
55 | "test": "jest"
56 | },
57 | "jest": {
58 | "moduleFileExtensions": ["ts", "js", "json"],
59 | "testRegex": "/dts-jest/.+\\.ts$",
60 | "transform": {"/dts-jest/.+\\.ts$": "dts-jest/transform"}
61 | }
62 | }
63 | ```
64 |
65 | This setup allow you to test files `**/dts-jest/**/*.ts` via `dts-jest`.
66 |
67 | ## Writing Tests
68 |
69 | The test cases must start with a comment `@dts-jest`, and the next line should be an expression that you want to test its type or value.
70 |
71 | (./dts-jest/example.ts)
72 |
73 | ```ts
74 | // @dts-jest:pass:snap
75 | Math.max(1);
76 |
77 | // @dts-jest:pass
78 | Math.min(1, 2, 3); //=> 1
79 |
80 | // @dts-jest:fail:snap
81 | Math.max('123');
82 |
83 | // @ts-expect-error:snap
84 | Math.max('123');
85 | ```
86 |
87 | ## Patterns
88 |
89 | ### Patterns for Testing
90 |
91 | ```ts
92 | // @dts-jest[flags] [description]
93 | expression //=> expected
94 |
95 | // @ts-expect-error[flags] [description]
96 | expression //=> expected
97 | ```
98 |
99 | _Note:_ [`@ts-expect-error`](https://devblogs.microsoft.com/typescript/announcing-typescript-3-9-beta/#ts-expect-error-comments) is treated as an alias of `@dts-jest:fail` in `dts-jest`.
100 |
101 | - description
102 | - optional
103 | - default: `expression`
104 | - flag
105 | - optional
106 | - for test
107 | - default: [`test`](https://facebook.github.io/jest/docs/en/api.html#testname-fn)
108 | - `:only`: [`test.only`](https://facebook.github.io/jest/docs/en/api.html#testonlyname-fn)
109 | - `:skip`: [`test.skip`](https://facebook.github.io/jest/docs/en/api.html#testskipname-fn)
110 | - for type assertion
111 | - default: none
112 | - `:show`: `console.log(type)`
113 | - `:pass`: `expect(() => type)`[`.not`](https://facebook.github.io/jest/docs/en/expect.html#not)[`.toThrow()`](https://facebook.github.io/jest/docs/en/expect.html#tothrowerror)
114 | - `:fail`: `expect(() => type)`[`.toThrow()`](https://facebook.github.io/jest/docs/en/expect.html#tothrowerror)
115 | - `:snap`:
116 | - snapshot inferred type or diagnostic message
117 | - `expect(type)`[`.toMatchSnapshot()`](https://facebook.github.io/jest/docs/en/expect.html#tomatchsnapshotoptionalstring)
118 | - `expect(type)`[`.toThrowErrorMatchingSnapshot()`](https://facebook.github.io/jest/docs/en/expect.html#tothrowerrormatchingsnapshot)
119 | - `:not-any`: `expect(type)`[`.not`](https://facebook.github.io/jest/docs/en/expect.html#not)[`.toBe("any")`](https://facebook.github.io/jest/docs/en/expect.html#tobevalue)
120 | - expected
121 | - optional
122 | - `//=> expected` or `/*=> expected */`
123 | - for value assertion
124 | - default: none
125 | - `?`: `console.log(value)`
126 | - `:error`: `expect(() => value)`[`.toThrow()`](https://facebook.github.io/jest/docs/en/expect.html#tothrowerror)
127 | - `:no-error`: `expect(() => value)`[`.not`](https://facebook.github.io/jest/docs/en/expect.html#not)[`.toThrow()`](https://facebook.github.io/jest/docs/en/expect.html#tothrowerror)
128 | - others: `expect(value)`[`.toEqual(expected)`](https://facebook.github.io/jest/docs/en/expect.html#toequalvalue)
129 |
130 | ### Patterns for Grouping
131 |
132 | Test cases after this pattern will be marked as that group.
133 |
134 | ```ts
135 | // @dts-jest:group[flag] [description]
136 | ```
137 |
138 | If you need a block scope for your tests you can use a Block Statement.
139 |
140 | ```ts
141 | // @dts-jest:group[flag] [description]
142 | {
143 | // your tests
144 | }
145 | ```
146 |
147 | - description
148 | - default: `''`
149 | - flag
150 | - default: [`describe`](https://facebook.github.io/jest/docs/en/api.html#describename-fn)
151 | - `:only`: [`describe.only`](https://facebook.github.io/jest/docs/en/api.html#describeonlyname-fn)
152 | - `:skip`: [`describe.skip`](https://facebook.github.io/jest/docs/en/api.html#describeskipname-fn)
153 |
154 | ### Patterns for File-Level Config
155 |
156 | File-level config uses the first comment to set, only docblock will be detected.
157 |
158 | ```ts
159 | /** @dts-jest [action:option] ... */
160 | ```
161 |
162 | - action:
163 | - `enable`: set to `true`
164 | - `disable`: set to `false`
165 | - option:
166 | - `test-type`: `test_type` option in [configs](#configs)
167 | - `test-value`: `test_value` option in [configs](#configs)
168 |
169 | ## Testing
170 |
171 | It's recommended you to run Jest in watching mode via `--watch` flag.
172 |
173 | ```sh
174 | npm run test -- --watch
175 | ```
176 |
177 | **NOTE**: If you had changed the version of `dts-jest`, you might have to use `--no-cache` flag since Jest may use the older cache.
178 |
179 | After running the [example tests](#writing-tests) with `npm run test`, you'll get the following result:
180 |
181 | ```text
182 | PASS tests/example.ts
183 | Math.max(1)
184 | ✓ (type) should not throw error
185 | ✓ (type) should match snapshot
186 | Math.max('123')
187 | ✓ (type) should throw error
188 | ✓ (type) should match snapshot
189 | Math.min(1, 2, 3)
190 | ✓ (type) should not throw error
191 |
192 | Snapshot Summary
193 | › 2 snapshots written in 1 test suite.
194 |
195 | Test Suites: 1 passed, 1 total
196 | Tests: 5 passed, 5 total
197 | Snapshots: 2 added, 2 total
198 | Time: 0.000s
199 | Ran all test suites.
200 | ```
201 |
202 | Since snapshot testing will always pass and write the result at the first time, it's reommended you to use `:show` flag to see the result first without writing results.
203 |
204 | (./dts-jest/example.ts)
205 |
206 | ```ts
207 | // @dts-jest:pass:show
208 | Math.max(1);
209 |
210 | // @dts-jest:fail:show
211 | Math.max('123');
212 |
213 | // @dts-jest:pass
214 | Math.min(1, 2, 3); //=> 1
215 | ```
216 |
217 | ```text
218 | PASS dts-jest/example.ts
219 | Math.max(1)
220 | ✓ (type) should show report
221 | ✓ (type) should not throw error
222 | Math.max('123')
223 | ✓ (type) should show report
224 | ✓ (type) should throw error
225 | Math.min(1, 2, 3)
226 | ✓ (type) should not throw error
227 |
228 | Test Suites: 1 passed, 1 total
229 | Tests: 5 passed, 5 total
230 | Snapshots: 0 total
231 | Time: 0.000s
232 | Ran all test suites.
233 |
234 | console.log dts-jest/example.ts:2
235 |
236 | Inferred
237 |
238 | Math.max(1)
239 |
240 | to be
241 |
242 | number
243 |
244 | console.log dts-jest/example.ts:5
245 |
246 | Inferring
247 |
248 | Math.max('123')
249 |
250 | but throw
251 |
252 | Argument of type '"123"' is not assignable to parameter of type 'number'.
253 | ```
254 |
255 | ## Configs
256 |
257 | Configs are in `_dts_jest_` field of Jest config `globals`.
258 |
259 | There are several options
260 |
261 | - test_type
262 | - default: `true`
263 | - enable type testing
264 | - [file-level config](#patterns-for-file-level-config) available
265 | - test_value
266 | - default: `false`
267 | - enable value testing
268 | - [file-level config](#patterns-for-file-level-config) available
269 | - enclosing_declaration
270 | - default: `false`
271 | - unwrap type alias
272 | - typescript
273 | - default: `typescript` (node resolution)
274 | - specify which path of typescript to use
275 | - `` available
276 | - compiler_options
277 | - default: `{}`
278 | - specify which *path of `tsconfig.json` (string)* ~~or *compilerOptions (object)*~~ (deprecated, does not support `typeRoots` for _object_) to use
279 | - type_format_flags
280 | - default: `ts.TypeFormatFlags.NoTruncation`
281 | - specify type format
282 | - transpile
283 | - default: `true`
284 | - transpile code before testing, only affect tests that needs to test value
285 | - transpiling code will cause line number incorrect, it's better to disable this option if possible
286 |
287 | For example:
288 |
289 | (./package.json)
290 |
291 | ```json
292 | {
293 | "jest": {
294 | "globals": {
295 | "_dts_jest_": {
296 | "compiler_options": {
297 | "strict": true,
298 | "target": "es6"
299 | }
300 | }
301 | }
302 | }
303 | }
304 | ```
305 |
306 | ## Generate diff-friendly snapshots
307 |
308 | Originally, snapshots and source content are in different files, it is hard to check their difference before/after, so here comes the `dts-jest-remap` for generating diff-friendly snapshots.
309 |
310 | (./tests/example.ts)
311 |
312 | ```ts
313 | // @dts-jest:snap
314 | Math.max(1, 2, 3);
315 | ```
316 |
317 | (./tests/`__snapshots__`/example.ts.snap) note this file is generated by Jest
318 |
319 | ```ts
320 | // Jest Snapshot v1, https://goo.gl/fbAQLP
321 | exports[`Math.max(1, 2, 3) 1`] = `"number"`;
322 | ```
323 |
324 | This command will combine both snapshots and source content in one file:
325 |
326 | ```sh
327 | dts-jest-remap ./tests/example.ts --outDir ./snapshots
328 | ```
329 |
330 | (./snapshots/example.ts)
331 |
332 | ```ts
333 | // @dts-jest:snap -> number
334 | Math.max(1, 2, 3);
335 | ```
336 |
337 | ```text
338 | Usage: dts-jest-remap [--outDir ] [--rename ] ...
339 |
340 | Options:
341 | --check, -c Throw error if target content is different from output
342 | content [boolean]
343 | --help, -h Show help [boolean]
344 | --listDifferent, -l Print the filenames of files that their target content is
345 | different from output content [boolean]
346 | --outDir, -o Redirect output structure to the directory [string]
347 | --rename, -r Rename output filename using template {{variable}},
348 | available variable: filename, basename, extname [string]
349 | --typescript, -t Specify which TypeScript source to use [string]
350 | --version, -v Show version number [boolean]
351 | ```
352 |
353 | ## Reporter
354 |
355 | If you'd like to know which typescript you are using, add `dts-jest/reporter` to your [Jest reporters](https://facebook.github.io/jest/docs/en/configuration.html#reporters-array-modulename-modulename-options), for example:
356 |
357 | ```json
358 | {
359 | "reporters": [
360 | "default",
361 | "dts-jest/reporter"
362 | ]
363 | }
364 | ```
365 |
366 | It'll show the TS version and path after testing:
367 |
368 | ```text
369 | [dts-jest] using TypeScript v0.0.0 from path/to/typescript
370 | ```
371 |
372 | ## FAQ
373 |
374 | - `Compiler option 'lib' requires a value of type list`
375 | - Arrays in `jest` > `globals` > `_dts_jest_` will be transformed into objects.
376 | - Consider to use [`setupFiles`](https://facebook.github.io/jest/docs/en/configuration.html#setupfiles-array) to set configs (`globals._dts_jest_ = { ... }`).
377 | - See [jest#2093](https://github.com/facebook/jest/issues/2093) for details.
378 | - `Debug Failure`
379 | - This is mostly caused by regex literal due to the printer bug [TypeScript#18071](https://github.com/Microsoft/TypeScript/issues/18071) (fixed in TS v2.6).
380 | - Workaround: use regex instance instead, e.g. `new RegExp('something')`.
381 |
382 | ## Development
383 |
384 | ```sh
385 | # lint
386 | yarn run lint
387 |
388 | # test
389 | yarn run test
390 |
391 | # build
392 | yarn run build
393 | ```
394 |
395 | ## Related
396 |
397 | - [dtslint](https://github.com/Microsoft/dtslint): A utility built on TSLint for linting TypeScript declaration (.d.ts) files
398 | - [typings-checker](https://github.com/danvk/typings-checker): Positive and negative assertions about TypeScript types and errors
399 |
400 | ## License
401 |
402 | MIT © [Ika](https://github.com/ikatyang)
403 |
--------------------------------------------------------------------------------
/bin/dts-jest-remap.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var dts_jest = require('../lib/index');
4 | var remap_cli_parser = dts_jest.create_remap_cli_parser();
5 | var remap_cli_args = remap_cli_parser(process.argv.slice(2));
6 |
7 | dts_jest.remap_cli(remap_cli_args);
8 |
--------------------------------------------------------------------------------
/fixtures/create-assertion-expression/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass:snap:show
2 | Math.max(1, 2, 3); //=> 3
3 |
4 | // @dts-jest:fail
5 | Math.abs(-1); //=> ?
6 |
7 | // @dts-jest
8 | Math.cos(Math.PI); //=> :no-error
9 |
10 | // @dts-jest
11 | Math.tan(Math.PI); //=> :error
12 |
13 | // @dts-jest:pass:snap:show
14 | Math.sin(Math.PI); //=> ?
15 |
16 | // @dts-jest:snap
17 | Object.assign({ a: 1 }, { b: 2 }, { c: 3 }); /*=>
18 | {
19 | a: 1,
20 | b: 2,
21 | c: 3,
22 | }
23 | */
24 |
25 | // @dts-jest
26 | Math.min(3, 2, 1);
27 |
28 | // @dts-jest:not-any
29 | Math.min(3, 2, 1);
30 |
--------------------------------------------------------------------------------
/fixtures/create-setup-expression/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass:snap:show
2 | Math.max(1, 2, 3); //=> 3
3 |
4 | // @dts-jest:fail
5 | Math.abs(-1); //=> ?
6 |
--------------------------------------------------------------------------------
/fixtures/create-snapshots/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass
2 | Math.max(1, 2, 3);
3 |
4 | // @dts-jest:pass
5 | Math.abs(-1);
6 |
7 | // @dts-jest:pass
8 | Math.abs('123');
9 |
10 | // @dts-jest:pass
11 | Object.assign<{ a: 1 }, { b: 2 }>({ c: 3 }, { d: 4 });
12 |
--------------------------------------------------------------------------------
/fixtures/create-snapshots/no-assertion.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | Math.max(1, 2, 3);
3 |
--------------------------------------------------------------------------------
/fixtures/create-snapshots/ts-expect-error.ts:
--------------------------------------------------------------------------------
1 | // @ts-expect-error
2 | Math.abs('123');
3 |
--------------------------------------------------------------------------------
/fixtures/create-snapshots/unmatched.ts:
--------------------------------------------------------------------------------
1 | String.fromCharCode('str');
2 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/disable-test-type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest disable:test-type
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/disable-test-value.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest disable:test-value
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/enable-test-type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest enable:test-type
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/enable-test-value.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest enable:test-value
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/multi-option-value.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest enable:test-value disable:test-type
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/normal-comment.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest enable:test-type
2 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/second-docblock.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * first docblock
3 | */
4 |
5 | /**
6 | * second docblock
7 | *
8 | * @dts-jest enable:test-type
9 | */
10 |
--------------------------------------------------------------------------------
/fixtures/find-docblock-options/unexpected.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @dts-jest enable:nothing
3 | */
4 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-bodies/invalid-group.ts:
--------------------------------------------------------------------------------
1 | {
2 | // @dts-jest:group
3 |
4 | // @dts-jest
5 | Math.max(1, 2, 3);
6 | }
7 |
8 | // @dts-jest:group outside
9 |
10 | // @dts-jest
11 | Math.max(4, 5, 6);
12 |
13 | {
14 | // @dts-jest:group inside
15 |
16 | // @dts-jest
17 | Math.max(7, 8, 9);
18 | }
19 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-bodies/simple.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | Math.max(1, 2, 3);
3 |
4 | // @dts-jest
5 | Math.max(
6 | 1,
7 | 2,
8 | 3,
9 | );
10 |
11 | (() => {
12 | // @dts-jest
13 | Math.max(1, 2, 3);
14 | });
15 |
16 |
17 | (() => {
18 | // @dts-jest
19 | Math.max(
20 | 1,
21 | 2,
22 | 3,
23 | );
24 | });
25 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-bodies/unattachable.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | // @dts-jest description
3 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-footers/simple.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | Math.max(1, 2, 3); //=> 3
3 |
4 | // @dts-jest
5 | Math.max(1, 2, 3); //=> ?
6 |
7 | // @dts-jest
8 | Math.max(1, 2, 3); //=> :error
9 |
10 | // @dts-jest
11 | Math.max(1, 2, 3); //=> :no-error
12 |
13 | // @dts-jest
14 | Math.max(1, 2, 3); /*=> 3 */
15 |
16 | // @dts-jest
17 | Math.max(1, 2, 3); /*=> ? */
18 |
19 | // @dts-jest
20 | Math.max(1, 2, 3); /*=> :error */
21 |
22 | // @dts-jest
23 | Math.max(1, 2, 3); /*=> :no-error */
24 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-footers/unattachable.ts:
--------------------------------------------------------------------------------
1 | //=> ?
2 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/group.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group
4 | // @dts-jest ? 1
5 | // @dts-jest ? 2
6 | // @dts-jest:group A
7 | // @dts-jest A 1
8 | // @dts-jest A 2
9 | // @dts-jest:group B
10 | // @dts-jest B 1
11 | // @dts-jest B 2
12 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/method.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | // @dts-jest:only
3 | // @dts-jest:skip
4 |
5 | // @dts-jest:group
6 |
7 | // @dts-jest
8 | // @dts-jest:only
9 | // @dts-jest:skip
10 |
11 | // @dts-jest:group:only
12 |
13 | // @dts-jest
14 | // @dts-jest:only
15 | // @dts-jest:skip
16 |
17 | // @dts-jest:group:skip
18 | // @dts-jest
19 | // @dts-jest:only
20 | // @dts-jest:skip
21 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/simple.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest
2 | // @dts-jest:show
3 | // @dts-jest:snap
4 | // @dts-jest:snap:show
5 | // @dts-jest:pass
6 | // @dts-jest:pass:show
7 | // @dts-jest:pass:snap
8 | // @dts-jest:pass:snap:show
9 | // @dts-jest:fail
10 | // @dts-jest:fail:show
11 | // @dts-jest:fail:snap
12 | // @dts-jest:fail:snap:show
13 |
14 | // @dts-jest description
15 | // @dts-jest:pass description + flag
16 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/ts-expect-error-equivalent.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:fail
2 | // @dts-jest:fail:show
3 | // @dts-jest:fail:snap
4 | // @dts-jest:fail:snap:show
5 | // @dts-jest:fail description
6 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/ts-expect-error.ts:
--------------------------------------------------------------------------------
1 | // @ts-expect-error
2 | // @ts-expect-error:show
3 | // @ts-expect-error:snap
4 | // @ts-expect-error:snap:show
5 | // @ts-expect-error description
6 |
--------------------------------------------------------------------------------
/fixtures/find-trigger-headers/unmatched.ts:
--------------------------------------------------------------------------------
1 | // unmatched comment
2 |
--------------------------------------------------------------------------------
/fixtures/find-triggers/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:group A
2 |
3 | // @dts-jest:pass:snap
4 | Math.max(1, 2, 3); //=> 3
5 |
6 | // @dts-jest:fail
7 | Math.abs(-1); //=> ?
8 |
9 | // @dts-jest:group B
10 |
11 | // @dts-jest
12 | Math.cos(Math.PI);
13 |
14 | // @dts-jest
15 | Object.assign({ a: 1 }, { b: 2 }, { c: 3 }); /*=>
16 | {
17 | a: 1,
18 | b: 2,
19 | c: 3,
20 | }
21 | */
22 |
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/example/placeholder.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikatyang/dts-jest/86823fe5959be5143842f93c65cc7fafc63bdd64/fixtures/load-compiler-options/example/placeholder.ts
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/example/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "strict": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "allowSyntheticDefaultImports": true,
6 | "typeRoots": ["./types"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/example/types/index.d.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikatyang/dts-jest/86823fe5959be5143842f93c65cc7fafc63bdd64/fixtures/load-compiler-options/example/types/index.d.ts
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/invalid/placeholder.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikatyang/dts-jest/86823fe5959be5143842f93c65cc7fafc63bdd64/fixtures/load-compiler-options/invalid/placeholder.ts
--------------------------------------------------------------------------------
/fixtures/load-compiler-options/invalid/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["---"]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/group-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group:only A
4 | // @dts-jest A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/multi-group-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group:only A
4 | // @dts-jest A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 | // @dts-jest:group:only C
10 | // @dts-jest C1
11 | // @dts-jest C2
12 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/multi-mixed-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest:only 2
3 | // @dts-jest:only 3
4 | // @dts-jest:group A
5 | // @dts-jest A1
6 | // @dts-jest A2
7 | // @dts-jest:group B
8 | // @dts-jest B1
9 | // @dts-jest B2
10 | // @dts-jest:group:only C
11 | // @dts-jest C1
12 | // @dts-jest C2
13 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/multi-trigger-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest:only 2
3 | // @dts-jest:only 3
4 | // @dts-jest:group A
5 | // @dts-jest A1
6 | // @dts-jest A2
7 | // @dts-jest:group B
8 | // @dts-jest B1
9 | // @dts-jest B2
10 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/no-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group A
4 | // @dts-jest A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/trigger-only-in-group-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group:only A
4 | // @dts-jest:only A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/trigger-only-in-group-skip.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest 2
3 | // @dts-jest:group:skip A
4 | // @dts-jest:only A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 |
--------------------------------------------------------------------------------
/fixtures/normalize-trigger-header-methods/trigger-only.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest 1
2 | // @dts-jest:only 2
3 | // @dts-jest:group A
4 | // @dts-jest A1
5 | // @dts-jest A2
6 | // @dts-jest:group B
7 | // @dts-jest B1
8 | // @dts-jest B2
9 |
--------------------------------------------------------------------------------
/fixtures/remap-cli/__snapshots__/example.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Math.max(3, 2, 1) (type) should match snapshot 1`] = `"(mocked max)"`;
4 |
5 | exports[`description-min (type) should match snapshot 1`] = `"(mocked min)"`;
6 |
7 | exports[`A Math.abs(3) (type) should match snapshot 1`] = `"(mocked abs)"`;
8 |
9 | exports[`A description-sin (type) should match snapshot 1`] = `"(mocked sin)"`;
10 |
11 | exports[`B Math.cos(0) (type) should match snapshot 1`] = `"(mocked cos)"`;
12 |
13 | exports[`B Math.tan(0) (type) should match snapshot 1`] = `"(mocked tan 1)"`;
14 |
15 | exports[`B Math.tan(0) (type) should match snapshot 2`] = `"(mocked tan 2)"`;
16 |
--------------------------------------------------------------------------------
/fixtures/remap-cli/empty.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikatyang/dts-jest/86823fe5959be5143842f93c65cc7fafc63bdd64/fixtures/remap-cli/empty.ts
--------------------------------------------------------------------------------
/fixtures/remap-cli/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:snap
2 | Math.max(3, 2, 1);
3 |
4 | // @dts-jest:snap description-min
5 | Math.min(3, 2, 1);
6 |
7 | // @dts-jest:group A
8 |
9 | // @dts-jest:snap
10 | Math.abs(3);
11 |
12 | // @dts-jest:snap description-sin
13 | Math.sin(0);
14 |
15 | // @dts-jest:group B
16 |
17 | // @dts-jest:snap
18 | Math.cos(0);
19 |
20 | // @dts-jest:snap
21 | Math.tan(0);
22 |
23 | // @dts-jest:snap
24 | Math.tan(0);
25 |
--------------------------------------------------------------------------------
/fixtures/remap-cli/remapped.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:snap -> (mocked max)
2 | Math.max(3, 2, 1);
3 |
4 | // @dts-jest:snap description-min -> (mocked min)
5 | Math.min(3, 2, 1);
6 |
7 | // @dts-jest:group A
8 |
9 | // @dts-jest:snap -> (mocked abs)
10 | Math.abs(3);
11 |
12 | // @dts-jest:snap description-sin -> (mocked sin)
13 | Math.sin(0);
14 |
15 | // @dts-jest:group B
16 |
17 | // @dts-jest:snap -> (mocked cos)
18 | Math.cos(0);
19 |
20 | // @dts-jest:snap -> (mocked tan 1)
21 | Math.tan(0);
22 |
23 | // @dts-jest:snap -> (mocked tan 2)
24 | Math.tan(0);
25 |
--------------------------------------------------------------------------------
/fixtures/remap/__snapshots__/example.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Math.max(3, 2, 1) (type) should match snapshot 1`] = `"(mocked max)"`;
4 |
5 | exports[`description-min (type) should match snapshot 1`] = `"(mocked min)"`;
6 |
7 | exports[`A Math.abs(3) (type) should match snapshot 1`] = `"(mocked abs)"`;
8 |
9 | exports[`A description-sin (type) should match snapshot 1`] = `"(mocked sin)"`;
10 |
11 | exports[`B Math.cos(0) (type) should match snapshot 1`] = `"(mocked cos)"`;
12 |
13 | exports[`B Math.tan(0) (type) should match snapshot 1`] = `"(mocked tan 1)"`;
14 |
15 | exports[`B Math.tan(0) (type) should match snapshot 2`] = `"(mocked tan 2)"`;
16 |
--------------------------------------------------------------------------------
/fixtures/remap/__snapshots__/unmatched.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Math.max(1, 2, 3) (type) should match snapshot 1`] = `"number"`;
4 |
--------------------------------------------------------------------------------
/fixtures/remap/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:snap
2 | Math.max(3, 2, 1);
3 |
4 | // @dts-jest:snap description-min
5 | Math.min(3, 2, 1);
6 |
7 | // @dts-jest:group A
8 |
9 | // @dts-jest:snap
10 | Math.abs(3);
11 |
12 | // @dts-jest:snap description-sin
13 | Math.sin(0);
14 |
15 | // @dts-jest:group B
16 |
17 | // @dts-jest:snap
18 | Math.cos(0);
19 |
20 | // @dts-jest:snap
21 | Math.tan(0);
22 |
23 | // @dts-jest:snap
24 | Math.tan(0);
25 |
--------------------------------------------------------------------------------
/fixtures/remap/unmatched.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:snap
2 | Math.max(1, 2, 3);
3 |
4 | // @dts-jest:snap
5 | Math.max(4, 5, 6);
6 |
--------------------------------------------------------------------------------
/fixtures/runtime/example.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass
2 | Math.min(3, 2, 1);
3 |
4 | // @dts-jest:pass description-pass
5 | Math.min(3, 2, 1);
6 |
7 | // @dts-jest:fail
8 | Math.max('123');
9 |
10 | // @dts-jest:fail description-fail
11 | Math.max('123');
12 |
13 | // @dts-jest
14 | Math.max(1, 2, 3); //=> ?
15 |
16 | // @dts-jest description-value
17 | Math.max(1, 2, 3); //=> ?
18 |
--------------------------------------------------------------------------------
/fixtures/transform/all.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass
2 | Math.max(1); //=> 1
3 |
4 | // @dts-jest:group A
5 |
6 | // @dts-jest:pass
7 | Math.max(2); //=> 2
8 |
9 | // @dts-jest:pass
10 | Math.max(3); //=> 3
11 |
12 | // @dts-jest:group B
13 |
14 | // @dts-jest:show
15 | Math.max(4); //=> ?
16 |
17 | // @dts-jest:pass
18 | Object.assign({
19 | a: 1,
20 | }, {
21 | b: 2,
22 | }, {
23 | c: 3,
24 | }); /*=>
25 | {
26 | a: 1,
27 | b: 2,
28 | c: 3,
29 | }
30 | */
31 |
--------------------------------------------------------------------------------
/fixtures/transform/commonjs.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | // @dts-jest
4 | path.basename('path/to/somewhere'); //=> 'somewhere'
5 |
--------------------------------------------------------------------------------
/fixtures/transform/multi-line.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass
2 | Object.assign({
3 | a: 1,
4 | }, {
5 | b: 2,
6 | }, {
7 | c: 3,
8 | }); //=> 123
9 |
10 | // @dts-jest:pass
11 | Object.assign({
12 | a: 1,
13 | }, {
14 | b: 2,
15 | }, {
16 | c: 3,
17 | }); /*=>
18 | {
19 | a: 1,
20 | b: 2,
21 | c: 3,
22 | }
23 | */
24 |
--------------------------------------------------------------------------------
/fixtures/transform/no-footers.ts:
--------------------------------------------------------------------------------
1 | // @dts-jest:pass
2 | Math.max(1);
3 |
4 | // @dts-jest:group A
5 |
6 | // @dts-jest:pass
7 | Math.max(2);
8 |
9 | // @dts-jest:pass
10 | Math.max(3);
11 |
12 | // @dts-jest:group B
13 |
14 | // @dts-jest:pass
15 | Math.max(4);
16 |
17 | // @dts-jest:pass
18 | Object.assign({ a: 1 }, { b: 2 }, { c: 3 });
19 |
--------------------------------------------------------------------------------
/jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "testEnvironment": "node",
3 | "moduleFileExtensions": ["ts", "js", "json"],
4 | "testMatch": ["**/*.test.ts"],
5 | "transform": {"\\.ts$": "ts-jest"},
6 | "coverageReporters": ["lcov", "text-summary"],
7 | "collectCoverageFrom": ["src/**/*.ts"],
8 | "globals": {
9 | "ts-jest": {
10 | "isolatedModules": true
11 | }
12 | },
13 | "coverageThreshold": {
14 | "global": {
15 | "branches": 100,
16 | "functions": 100,
17 | "lines": 100,
18 | "statements": 100
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dts-jest",
3 | "version": "26.0.2",
4 | "description": "A preprocessor for Jest to snapshot test TypeScript declaration (.d.ts) files",
5 | "keywords": [
6 | "jest",
7 | "jest-transform",
8 | "test",
9 | "typescript",
10 | "typescript-declarations"
11 | ],
12 | "main": "lib/index.js",
13 | "types": "lib/index.d.ts",
14 | "bin": {
15 | "dts-jest-remap": "bin/dts-jest-remap.js"
16 | },
17 | "repository": "https://github.com/ikatyang/dts-jest",
18 | "homepage": "https://github.com/ikatyang/dts-jest#readme",
19 | "author": {
20 | "name": "Ika",
21 | "email": "ikatyang@gmail.com",
22 | "url": "https://github.com/ikatyang"
23 | },
24 | "license": "MIT",
25 | "scripts": {
26 | "prepublish": "yarn run build",
27 | "lint": "prettier src --check",
28 | "test": "jest -c ./jest.json --no-cache",
29 | "test-integration": "jest -c ./tests/jest.json --no-cache",
30 | "remap-integration": "node ./bin/dts-jest-remap ./tests/example.ts --rename '{{basename}}.snap.{{extname}}'",
31 | "prebuild": "rm -rf ./lib",
32 | "build": "tsc -p ./tsconfig.build.json",
33 | "release": "standard-version"
34 | },
35 | "dependencies": {
36 | "globby": "^6.1.0",
37 | "jest-snapshot-parser": "^1.0.0",
38 | "lodash.intersection": "^4.4.0",
39 | "make-dir": "^1.0.0",
40 | "pretty-format": "^21.0.0",
41 | "ts-comment": "^1.1.0",
42 | "tslib": "^2.4.0",
43 | "yargs": "^14.2.3"
44 | },
45 | "devDependencies": {
46 | "@types/globby": "6.1.0",
47 | "@types/lodash.intersection": "4.4.6",
48 | "@types/make-dir": "1.0.3",
49 | "@types/node": "12.12.6",
50 | "@types/pretty-format": "20.0.1",
51 | "@types/yargs": "8.0.3",
52 | "jest": "28.1.0",
53 | "prettier": "2.6.2",
54 | "standard-version": "9.3.2",
55 | "ts-jest": "28.0.2",
56 | "typescript": "5.0.3"
57 | },
58 | "peerDependencies": {
59 | "jest": ">= 28.0.0",
60 | "typescript": ">= 4.0.0"
61 | },
62 | "engines": {
63 | "node": ">= 12"
64 | },
65 | "files": [
66 | "/bin/**/*",
67 | "/lib/**/*",
68 | "/*.js"
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "ikatyang:library"
3 | }
4 |
--------------------------------------------------------------------------------
/reporter.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/index').Reporter;
2 |
--------------------------------------------------------------------------------
/src/__snapshots__/remap-cli.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should log diff filenames correctly with --listDifferent 1`] = `
4 | "Using TypeScript vX.Y.Z-mocked from /node_modules/typescript/lib/typescript.js
5 |
6 | Listing files that their target content is different from output content:
7 |
8 | /fixtures/remap-cli/example.ts -> /fixtures/remap-cli/empty.ts"
9 | `;
10 |
11 | exports[`should log diff filenames correctly with --listDifferent if there is no such output file 1`] = `
12 | "Using TypeScript vX.Y.Z-mocked from /node_modules/typescript/lib/typescript.js
13 |
14 | Listing files that their target content is different from output content:
15 |
16 | /fixtures/remap-cli/example.ts -> /fixtures/remap-cli/___not_exist___.ts"
17 | `;
18 |
19 | exports[`should log typescript information 1`] = `"Using TypeScript vX.Y.Z-mocked from /node_modules/typescript/lib/typescript.js"`;
20 |
21 | exports[`should log warning if there is no matched file 1`] = `"There is no matched file."`;
22 |
23 | exports[`should remap correctly 1`] = `
24 | "// @dts-jest:snap -> (mocked max)
25 | Math.max(3, 2, 1);
26 |
27 | // @dts-jest:snap description-min -> (mocked min)
28 | Math.min(3, 2, 1);
29 |
30 | // @dts-jest:group A
31 |
32 | // @dts-jest:snap -> (mocked abs)
33 | Math.abs(3);
34 |
35 | // @dts-jest:snap description-sin -> (mocked sin)
36 | Math.sin(0);
37 |
38 | // @dts-jest:group B
39 |
40 | // @dts-jest:snap -> (mocked cos)
41 | Math.cos(0);
42 |
43 | // @dts-jest:snap -> (mocked tan 1)
44 | Math.tan(0);
45 |
46 | // @dts-jest:snap -> (mocked tan 2)
47 | Math.tan(0);
48 | "
49 | `;
50 |
51 | exports[`should throw error if differences exist with --check 1`] = `"Difference(s) detected"`;
52 |
53 | exports[`should throw error if input and output file are the same 1`] = `
54 | "Input and output filename cannot be the same:
55 |
56 | /fixtures/remap-cli/example.ts
57 |
58 | Consider adjusting option \`--outDir\` or \`--rename\` to redirect output"
59 | `;
60 |
61 | exports[`should throw error if there is no input 1`] = `
62 | "Usage: dts-jest-remap [--outDir ] [--rename ] ...
63 |
64 | Options:
65 | --check, -c Throw error if target content is different from output
66 | content [boolean]
67 | --help, -h Show help [boolean]
68 | --listDifferent, -l Print the filenames of files that their target content is
69 | different from output content [boolean]
70 | --outDir, -o Redirect output structure to the directory [string]
71 | --rename, -r Rename output filename using template {{variable}},
72 | available variable: filename, basename, extname [string]
73 | --typescript, -t Specify which TypeScript source to use [string]
74 | --version, -v Show version number [boolean]
75 |
76 | Documentation: https://github.com/ikatyang/dts-jest#readme
77 |
78 | Not enough non-option arguments: got 0, need at least 1"
79 | `;
80 |
81 | exports[`should throw error with differences if differences exist with --check and --listDifferent 1`] = `
82 | "Listing files that their target content is different from output content:
83 |
84 | /fixtures/remap-cli/example.ts -> /fixtures/remap-cli/empty.ts
85 |
86 | Difference(s) detected"
87 | `;
88 |
89 | exports[`should write path correctly with --outDir 1`] = `"/snapshots/example.ts"`;
90 |
91 | exports[`should write path correctly with --rename 1`] = `"/fixtures/remap-cli/example.snap.ts"`;
92 |
--------------------------------------------------------------------------------
/src/__snapshots__/remap.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should remap correctly 1`] = `
4 | "// @dts-jest:snap -> (mocked max)
5 | Math.max(3, 2, 1);
6 |
7 | // @dts-jest:snap description-min -> (mocked min)
8 | Math.min(3, 2, 1);
9 |
10 | // @dts-jest:group A
11 |
12 | // @dts-jest:snap -> (mocked abs)
13 | Math.abs(3);
14 |
15 | // @dts-jest:snap description-sin -> (mocked sin)
16 | Math.sin(0);
17 |
18 | // @dts-jest:group B
19 |
20 | // @dts-jest:snap -> (mocked cos)
21 | Math.cos(0);
22 |
23 | // @dts-jest:snap -> (mocked tan 1)
24 | Math.tan(0);
25 |
26 | // @dts-jest:snap -> (mocked tan 2)
27 | Math.tan(0);
28 | "
29 | `;
30 |
31 | exports[`should throw error if there is non-string value in snapshot value 1`] = `"Snapshot value should be a string, but got 123"`;
32 |
33 | exports[`should throw error if there is unmatched snapshot 1`] = `"Unmatched snapshot title \`Math.max(4, 5, 6) (type) should match snapshot 1\`"`;
34 |
--------------------------------------------------------------------------------
/src/__snapshots__/reporter.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should report correctly 1`] = `
4 | "
5 | [dts-jest] using TypeScript vX.Y.Z-mocked from /node_modules/typescript/lib/typescript.js
6 |
7 | "
8 | `;
9 |
--------------------------------------------------------------------------------
/src/__snapshots__/runtime.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`#get_type_inference_or_diagnostic() should return diagnostic on fail line 1`] = `"Argument of type 'string' is not assignable to parameter of type 'number'."`;
4 |
5 | exports[`#get_type_inference_or_diagnostic() should return inference on non-fail line 1`] = `"number"`;
6 |
7 | exports[`#get_type_inference_or_throw_diagnostic() should return inference on non-fail line 1`] = `"number"`;
8 |
9 | exports[`#get_type_inference_or_throw_diagnostic() should throw diagnostic on fail line 1`] = `"Argument of type 'string' is not assignable to parameter of type 'number'."`;
10 |
11 | exports[`#get_type_report() should return diagnostic report on fail line 1`] = `
12 | "
13 | Inferring
14 |
15 | Math.max('123')
16 |
17 | but throw
18 |
19 | Argument of type 'string' is not assignable to parameter of type 'number'.
20 | "
21 | `;
22 |
23 | exports[`#get_type_report() should return diagnostic report with description on fail line with description 1`] = `
24 | "
25 | description-fail
26 |
27 | Inferring
28 |
29 | Math.max('123')
30 |
31 | but throw
32 |
33 | Argument of type 'string' is not assignable to parameter of type 'number'.
34 | "
35 | `;
36 |
37 | exports[`#get_type_report() should return inference report on non-fail line 1`] = `
38 | "
39 | Inferred
40 |
41 | Math.min(3, 2, 1)
42 |
43 | to be
44 |
45 | number
46 | "
47 | `;
48 |
49 | exports[`#get_type_report() should return inference report on non-fail line with line info while transpiled 1`] = `
50 | "
51 | (/fixtures/runtime/example.ts:2)
52 |
53 | Inferred
54 |
55 | Math.min(3, 2, 1)
56 |
57 | to be
58 |
59 | number
60 | "
61 | `;
62 |
63 | exports[`#get_type_report() should return inference report with description on non-fail line with description 1`] = `
64 | "
65 | description-pass
66 |
67 | Inferred
68 |
69 | Math.min(3, 2, 1)
70 |
71 | to be
72 |
73 | number
74 | "
75 | `;
76 |
77 | exports[`#get_value_report() should return error message report for fail getter 1`] = `
78 | "
79 | Evaluating
80 |
81 | Math.max(1, 2, 3)
82 |
83 | but throw
84 |
85 | Example Error
86 | "
87 | `;
88 |
89 | exports[`#get_value_report() should return error message report with description on desciption line for fail getter 1`] = `
90 | "
91 | Evaluating
92 |
93 | Math.max(1, 2, 3)
94 |
95 | but throw
96 |
97 | Example Error
98 | "
99 | `;
100 |
101 | exports[`#get_value_report() should return formatted value report for non-fail getter 1`] = `
102 | "
103 | description-value
104 |
105 | Evaluated
106 |
107 | Math.max(1, 2, 3)
108 |
109 | to be
110 |
111 | Object {
112 | \\"example\\": \\"value\\",
113 | }
114 | "
115 | `;
116 |
117 | exports[`#get_value_report() should return formatted value report for non-fail getter with line info while transpiled 1`] = `
118 | "
119 | (/fixtures/runtime/example.ts:17)
120 |
121 | description-value
122 |
123 | Evaluated
124 |
125 | Math.max(1, 2, 3)
126 |
127 | to be
128 |
129 | Object {
130 | \\"example\\": \\"value\\",
131 | }
132 | "
133 | `;
134 |
135 | exports[`#get_value_report() should return formatted value report with description on desciption for non-fail getter 1`] = `
136 | "
137 | description-value
138 |
139 | Evaluated
140 |
141 | Math.max(1, 2, 3)
142 |
143 | to be
144 |
145 | Object {
146 | \\"example\\": \\"value\\",
147 | }
148 | "
149 | `;
150 |
--------------------------------------------------------------------------------
/src/__snapshots__/transform.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should retain line number while transforming 1`] = `
4 | ";// @dts-jest:pass
5 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(1) }).not.toThrow() });test(\\"(value) should equal to 123\\", function () { expect(Object.assign({ a: 1, }, { b: 2, }, { c: 3, })).toEqual(123) }) })
6 |
7 |
8 |
9 |
10 |
11 | //=> 123
12 |
13 | // @dts-jest:pass
14 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(10) }).not.toThrow() });test(\\"(value) should equal to { a: 1, b: 2, c: 3, }\\", function () { expect(Object.assign({ a: 1, }, { b: 2, }, { c: 3, })).toEqual({ a: 1, b: 2, c: 3, }) }) })
15 |
16 |
17 |
18 |
19 |
20 | /*=>
21 | {
22 | a: 1,
23 | b: 2,
24 | c: 3,
25 | }
26 | */
27 | "
28 | `;
29 |
30 | exports[`should transform correctly with { test_type: false, test_value: false } 1`] = `
31 | ";
32 | describe(\\"Math.max(1)\\", function () { })
33 |
34 | describe(\\"A\\", function () {
35 |
36 |
37 | describe(\\"Math.max(2)\\", function () { })
38 |
39 |
40 | describe(\\"Math.max(3)\\", function () { })
41 |
42 | });describe(\\"B\\", function () {
43 |
44 |
45 | describe(\\"Math.max(4)\\", function () { })
46 |
47 |
48 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { })
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | });"
62 | `;
63 |
64 | exports[`should transform correctly with { test_type: false, test_value: true } 1`] = `
65 | ";// @dts-jest:pass
66 | describe(\\"Math.max(1)\\", function () { test(\\"(value) should equal to 1\\", function () { expect(Math.max(1)).toEqual(1) }) }) //=> 1
67 |
68 | describe(\\"A\\", function () { // @dts-jest:group A
69 |
70 | // @dts-jest:pass
71 | describe(\\"Math.max(2)\\", function () { test(\\"(value) should equal to 2\\", function () { expect(Math.max(2)).toEqual(2) }) }) //=> 2
72 |
73 | // @dts-jest:pass
74 | describe(\\"Math.max(3)\\", function () { test(\\"(value) should equal to 3\\", function () { expect(Math.max(3)).toEqual(3) }) }) //=> 3
75 |
76 | });describe(\\"B\\", function () { // @dts-jest:group B
77 |
78 | // @dts-jest:show
79 | describe(\\"Math.max(4)\\", function () { test(\\"(value) should show report\\", function () { console.log(_dts_jest_runtime_.get_value_report(14, function () { return Math.max(4) })) }) }) //=> ?
80 |
81 | // @dts-jest:pass
82 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { test(\\"(value) should equal to { a: 1, b: 2, c: 3, }\\", function () { expect(Object.assign({ a: 1, }, { b: 2, }, { c: 3, })).toEqual({ a: 1, b: 2, c: 3, }) }) })
83 |
84 |
85 |
86 |
87 |
88 | /*=>
89 | {
90 | a: 1,
91 | b: 2,
92 | c: 3,
93 | }
94 | */
95 | });"
96 | `;
97 |
98 | exports[`should transform correctly with { test_type: true, test_value: false } 1`] = `
99 | ";
100 | describe(\\"Math.max(1)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(1) }).not.toThrow() }) })
101 |
102 | describe(\\"A\\", function () {
103 |
104 |
105 | describe(\\"Math.max(2)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(6) }).not.toThrow() }) })
106 |
107 |
108 | describe(\\"Math.max(3)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(9) }).not.toThrow() }) })
109 |
110 | });describe(\\"B\\", function () {
111 |
112 |
113 | describe(\\"Math.max(4)\\", function () { test(\\"(type) should show report\\", function () { console.log(_dts_jest_runtime_.get_type_report(14)) }) })
114 |
115 |
116 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(17) }).not.toThrow() }) })
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | });"
130 | `;
131 |
132 | exports[`should transform correctly with { test_type: true, test_value: true } 1`] = `
133 | ";// @dts-jest:pass
134 | describe(\\"Math.max(1)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(1) }).not.toThrow() });test(\\"(value) should equal to 1\\", function () { expect(Math.max(1)).toEqual(1) }) }) //=> 1
135 |
136 | describe(\\"A\\", function () { // @dts-jest:group A
137 |
138 | // @dts-jest:pass
139 | describe(\\"Math.max(2)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(6) }).not.toThrow() });test(\\"(value) should equal to 2\\", function () { expect(Math.max(2)).toEqual(2) }) }) //=> 2
140 |
141 | // @dts-jest:pass
142 | describe(\\"Math.max(3)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(9) }).not.toThrow() });test(\\"(value) should equal to 3\\", function () { expect(Math.max(3)).toEqual(3) }) }) //=> 3
143 |
144 | });describe(\\"B\\", function () { // @dts-jest:group B
145 |
146 | // @dts-jest:show
147 | describe(\\"Math.max(4)\\", function () { test(\\"(type) should show report\\", function () { console.log(_dts_jest_runtime_.get_type_report(14)) });test(\\"(value) should show report\\", function () { console.log(_dts_jest_runtime_.get_value_report(14, function () { return Math.max(4) })) }) }) //=> ?
148 |
149 | // @dts-jest:pass
150 | describe(\\"Object.assign({\\\\n a: 1,\\\\n }, {\\\\n b: 2,\\\\n }, {\\\\n c: 3,\\\\n })\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(17) }).not.toThrow() });test(\\"(value) should equal to { a: 1, b: 2, c: 3, }\\", function () { expect(Object.assign({ a: 1, }, { b: 2, }, { c: 3, })).toEqual({ a: 1, b: 2, c: 3, }) }) })
151 |
152 |
153 |
154 |
155 |
156 | /*=>
157 | {
158 | a: 1,
159 | b: 2,
160 | c: 3,
161 | }
162 | */
163 | });"
164 | `;
165 |
166 | exports[`should transform to commonjs if set 1`] = `
167 | ";\\"use strict\\";
168 | Object.defineProperty(exports, \\"__esModule\\", { value: true });
169 | var path = require(\\"path\\");
170 | // @dts-jest
171 | describe(\\"path.basename('path/to/somewhere')\\", function () { test(\\"(value) should equal to 'somewhere'\\", function () { expect(path.basename('path/to/somewhere')).toEqual('somewhere'); }); }); //=> 'somewhere'
172 | "
173 | `;
174 |
175 | exports[`should transform to fake environment for no-footers even if test_value = true 1`] = `
176 | ";
177 | describe(\\"Math.max(1)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(1) }).not.toThrow() }) })
178 |
179 | describe(\\"A\\", function () {
180 |
181 |
182 | describe(\\"Math.max(2)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(6) }).not.toThrow() }) })
183 |
184 |
185 | describe(\\"Math.max(3)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(9) }).not.toThrow() }) })
186 |
187 | });describe(\\"B\\", function () {
188 |
189 |
190 | describe(\\"Math.max(4)\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(14) }).not.toThrow() }) })
191 |
192 |
193 | describe(\\"Object.assign({ a: 1 }, { b: 2 }, { c: 3 })\\", function () { test(\\"(type) should not throw error\\", function () { expect(function () { _dts_jest_runtime_.get_type_inference_or_throw_diagnostic(17) }).not.toThrow() }) })
194 | });"
195 | `;
196 |
--------------------------------------------------------------------------------
/src/definitions.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | const package_json = require('../package.json');
4 |
5 | export const package_name = package_json.name;
6 | export const package_homepage = package_json.homepage;
7 | export const package_remap_bin = Object.keys(package_json.bin)[0];
8 |
9 | export const config_namespace = '_dts_jest_';
10 | export const runtime_namespace = '_dts_jest_runtime_';
11 |
12 | export const env_root_dir = 'DTS_JEST_ROOT_DIR';
13 |
14 | export const runtime_indent_spaces = 2;
15 |
16 | export const docblock_option_regex = /@dts-jest((?: +[^:]+:[^ ]+)+)/;
17 | export enum DocblockOptionMatchIndex {
18 | Input,
19 | Options,
20 | }
21 | export enum DocblockOptionValue {
22 | EnableTestType = 'enable:test-type',
23 | EnableTestValue = 'enable:test-value',
24 | DisableTestType = 'disable:test-type',
25 | DisableTestValue = 'disable:test-value',
26 | }
27 | export interface DocblockOptions {
28 | test_type?: boolean;
29 | test_value?: boolean;
30 | }
31 |
32 | export const trigger_header_regex = /^\s*@dts-jest\b(:?\S*)\s*(.*)\s*$/;
33 | export enum TriggerHeaderMatchIndex {
34 | Input,
35 | Flags,
36 | Description,
37 | }
38 |
39 | export const trigger_footer_regex = /^=>\s*([\s\S]*)\s*$/;
40 | export enum TriggerFooterMatchIndex {
41 | Input,
42 | Value,
43 | }
44 |
45 | export enum TriggerHeaderFlags {
46 | None = 0,
47 | ':snap' = 1 << 0,
48 | ':show' = 1 << 1,
49 | ':pass' = 1 << 2,
50 | ':fail' = 1 << 3,
51 | ':only' = 1 << 4,
52 | ':skip' = 1 << 5,
53 | ':group' = 1 << 6,
54 | ':not-any' = 1 << 7,
55 | Assertion = TriggerHeaderFlags[':snap'] |
56 | TriggerHeaderFlags[':show'] |
57 | TriggerHeaderFlags[':pass'] |
58 | TriggerHeaderFlags[':fail'] |
59 | TriggerHeaderFlags[':not-any'],
60 | }
61 |
62 | export enum TriggerFooterFlag {
63 | Show = '?',
64 | Error = ':error',
65 | NoError = ':no-error',
66 | }
67 |
68 | export enum TestMethod {
69 | Test = 'test',
70 | Only = 'test.only',
71 | Skip = 'test.skip',
72 | }
73 |
74 | export enum GroupMethod {
75 | Test = 'describe',
76 | Only = 'describe.only',
77 | Skip = 'describe.skip',
78 | }
79 |
80 | export interface TriggerHeader {
81 | line: number;
82 | flags: TriggerHeaderFlags;
83 | method: TestMethod;
84 | description?: string;
85 | group?: TriggerGroup;
86 | }
87 |
88 | export interface TriggerBody {
89 | start: number;
90 | end: number;
91 | /**
92 | * raw expression text without trailing semicolon
93 | */
94 | text: string;
95 | /**
96 | * one line expression text without trailing semicolon
97 | */
98 | experssion: string;
99 | }
100 |
101 | export interface TriggerFooter {
102 | line: number;
103 | flag?: TriggerFooterFlag;
104 | expected?: string;
105 | }
106 |
107 | export interface Trigger {
108 | header: TriggerHeader;
109 | body: TriggerBody;
110 | footer?: TriggerFooter;
111 | }
112 |
113 | export interface TriggerGroup {
114 | line: number;
115 | method: GroupMethod;
116 | description?: string;
117 | }
118 |
119 | export interface Snapshot {
120 | line: number;
121 | inference?: string;
122 | diagnostic?: string;
123 | }
124 |
125 | export interface JestConfig {
126 | rootDir: string;
127 | globals: { [K in typeof config_namespace]?: RawConfig };
128 | }
129 |
130 | export interface RawConfig extends DocblockOptions {
131 | compiler_options?: string | Record;
132 | enclosing_declaration?: boolean;
133 | type_format_flags?: _ts.TypeFormatFlags;
134 | typescript?: string;
135 | transpile?: boolean;
136 | }
137 |
138 | export interface NormalizedConfig {
139 | test_type: boolean;
140 | test_value: boolean;
141 | compiler_options: _ts.CompilerOptions;
142 | file_names: string[];
143 | enclosing_declaration: boolean;
144 | type_format_flags: _ts.TypeFormatFlags;
145 | typescript: typeof _ts;
146 | typescript_path: string;
147 | transpile: boolean;
148 | }
149 |
--------------------------------------------------------------------------------
/src/helpers/cwd-serializer.ts:
--------------------------------------------------------------------------------
1 | export const cwd_serializer: jest.SnapshotSerializerPlugin = {
2 | print: (value: string, serializer) =>
3 | serializer(value.replace(new RegExp(process.cwd(), 'g'), '')),
4 | test: (value: any) =>
5 | typeof value === 'string' && value.indexOf(process.cwd()) !== -1,
6 | };
7 |
--------------------------------------------------------------------------------
/src/helpers/load-fixture.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import * as _ts from 'typescript';
4 | import { create_source_file } from '../utils/create-source-file';
5 |
6 | export const get_fixture_filename = (id: string) =>
7 | path.resolve(__dirname, '../../fixtures', id);
8 |
9 | export const load_fixture = (id: string) =>
10 | fs.readFileSync(get_fixture_filename(id), 'utf8');
11 |
12 | export const load_fixture_source_file = (id: string, ts: typeof _ts) => {
13 | const source = load_fixture(id);
14 | return create_source_file(id, source, ts);
15 | };
16 |
--------------------------------------------------------------------------------
/src/helpers/trigger-header-serializer.ts:
--------------------------------------------------------------------------------
1 | import { TriggerHeader, TriggerHeaderFlags } from '../definitions';
2 |
3 | export const trigger_header_serializer: jest.SnapshotSerializerPlugin = {
4 | print: (value: TriggerHeader, serializer) => {
5 | const flag_literals: string[] = [];
6 |
7 | let { flags } = value;
8 |
9 | for (let base = 1; flags !== 0; flags >>= 1, base <<= 1) {
10 | if (flags & 1) {
11 | flag_literals.push(TriggerHeaderFlags[base]);
12 | }
13 | }
14 |
15 | return serializer({ ...value, flag_literals });
16 | },
17 | test: (value: any) =>
18 | typeof value === 'object' &&
19 | typeof value.flags === 'number' &&
20 | !('flag_literals' in value),
21 | };
22 |
--------------------------------------------------------------------------------
/src/helpers/version-serializer.ts:
--------------------------------------------------------------------------------
1 | const version_regex = /\bv[0-9]+\.[0-9]+\.[0-9]+\b/g;
2 |
3 | export const version_serializer: jest.SnapshotSerializerPlugin = {
4 | print: (value: string, serializer) =>
5 | serializer(value.replace(version_regex, 'vX.Y.Z-mocked')),
6 | test: (value: any) => typeof value === 'string' && version_regex.test(value),
7 | };
8 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import * as dts_jest from './index';
2 |
3 | it(`should have method 'transform'`, () => {
4 | expect(dts_jest.transform).toBeInstanceOf(Function);
5 | });
6 |
7 | it(`should have method 'setup'`, () => {
8 | expect(dts_jest.setup).toBeInstanceOf(Function);
9 | });
10 |
11 | it(`should have class 'Reporter'`, () => {
12 | expect(dts_jest.Reporter).toBeInstanceOf(Function);
13 | });
14 |
15 | it(`should have method 'remap'`, () => {
16 | expect(dts_jest.remap).toBeInstanceOf(Function);
17 | });
18 |
19 | it(`should have method 'remap_cli'`, () => {
20 | expect(dts_jest.remap_cli).toBeInstanceOf(Function);
21 | });
22 |
23 | it(`should have method 'create_remap_cli_parser'`, () => {
24 | expect(dts_jest.create_remap_cli_parser).toBeInstanceOf(Function);
25 | });
26 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './remap';
2 | export * from './remap-cli';
3 | export * from './remap-cli-parser';
4 | export * from './reporter';
5 | export * from './setup';
6 | export * from './transform';
7 |
--------------------------------------------------------------------------------
/src/remap-cli-parser.ts:
--------------------------------------------------------------------------------
1 | import yargs = require('yargs');
2 | import { package_homepage, package_remap_bin } from './definitions';
3 | import { RemapCliArguments, RemapCliOptions } from './remap-cli';
4 |
5 | export const create_remap_cli_parser = () => {
6 | let parser = yargs
7 | .usage(
8 | `Usage: ${package_remap_bin} [--outDir ] [--rename ] ...`,
9 | )
10 | .demandCommand(1)
11 | .epilogue(`Documentation: ${package_homepage}`);
12 |
13 | const options = get_cli_options();
14 | const special_options = {
15 | help: { alias: 'h' },
16 | version: { alias: 'v' },
17 | };
18 |
19 | const is_special_key = (key: string): key is keyof typeof special_options =>
20 | key in special_options;
21 |
22 | [...Object.keys(options), ...Object.keys(special_options)]
23 | .sort()
24 | .forEach(key => {
25 | parser = is_special_key(key)
26 | ? parser[key]().alias(key, special_options[key].alias)
27 | : parser.option(key, options[key as keyof RemapCliOptions]!);
28 | });
29 |
30 | return parser.parse.bind(parser) as (argv: string[]) => RemapCliArguments;
31 | };
32 |
33 | function get_cli_options(): {
34 | [K in keyof RemapCliOptions]: {
35 | type: 'boolean' | 'string';
36 | alias: string;
37 | description: string;
38 | };
39 | } {
40 | return {
41 | check: {
42 | alias: 'c',
43 | type: 'boolean',
44 | description:
45 | 'Throw error if target content is different from output content',
46 | },
47 | listDifferent: {
48 | alias: 'l',
49 | type: 'boolean',
50 | description:
51 | 'Print the filenames of files that their target content is different from output content',
52 | },
53 | outDir: {
54 | alias: 'o',
55 | type: 'string',
56 | description: 'Redirect output structure to the directory',
57 | },
58 | rename: {
59 | alias: 'r',
60 | type: 'string',
61 | description:
62 | 'Rename output filename using template {{variable}}, available variable: filename, basename, extname',
63 | },
64 | typescript: {
65 | alias: 't',
66 | type: 'string',
67 | description: 'Specify which TypeScript source to use',
68 | },
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/src/remap-cli.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import { cwd_serializer } from './helpers/cwd-serializer';
3 | import { get_fixture_filename } from './helpers/load-fixture';
4 | import { version_serializer } from './helpers/version-serializer';
5 | import { remap_cli } from './remap-cli';
6 | import { create_remap_cli_parser } from './remap-cli-parser';
7 |
8 | expect.addSnapshotSerializer(cwd_serializer);
9 | expect.addSnapshotSerializer(version_serializer);
10 |
11 | const console_log = jest.spyOn(console, 'log');
12 | const console_error = jest.spyOn(console, 'error');
13 | const wrtie_file_sync = jest.spyOn(fs, 'writeFileSync');
14 | const process_exit = jest.spyOn(process, 'exit');
15 |
16 | beforeAll(() => {
17 | console_log.mockReturnThis();
18 | console_error.mockReturnThis();
19 | wrtie_file_sync.mockReturnThis();
20 | process_exit.mockImplementation(code => {
21 | if (code !== 0) {
22 | throw new Error(`Exit with code ${code}`);
23 | }
24 | });
25 | });
26 |
27 | beforeEach(() => {
28 | console_log.mockClear();
29 | console_error.mockClear();
30 | wrtie_file_sync.mockClear();
31 | });
32 |
33 | afterAll(() => {
34 | console_log.mockRestore();
35 | console_error.mockRestore();
36 | wrtie_file_sync.mockRestore();
37 | process_exit.mockRestore();
38 | });
39 |
40 | const remap_cli_parser = create_remap_cli_parser();
41 |
42 | it('should remap correctly', () => {
43 | run_cli('example.ts', ['--rename', '{{basename}}.snap.{{extname}}']);
44 | expect(wrtie_file_sync.mock.calls[0][1]).toMatchSnapshot();
45 | });
46 |
47 | it('should write path correctly with --rename', () => {
48 | run_cli('example.ts', ['--rename', '{{basename}}.snap.{{extname}}']);
49 | expect(wrtie_file_sync.mock.calls[0][0]).toMatchSnapshot();
50 | });
51 |
52 | it('should write path correctly with --outDir', () => {
53 | run_cli('example.ts', ['--outDir', './snapshots']);
54 | expect(wrtie_file_sync.mock.calls[0][0]).toMatchSnapshot();
55 | });
56 |
57 | it('should log warning if there is no matched file', () => {
58 | run_cli('---not-exist---', []);
59 | expect(get_console_content(console_log)).toMatchSnapshot();
60 | });
61 |
62 | it('should log diff filenames correctly with --listDifferent', () => {
63 | run_cli('example.ts', ['--listDifferent', '--rename', 'empty.ts']);
64 | expect(get_console_content(console_log)).toMatchSnapshot();
65 | });
66 |
67 | it('should log diff filenames correctly with --listDifferent if there is no such output file', () => {
68 | run_cli('example.ts', ['--listDifferent', '--rename', '___not_exist___.ts']);
69 | expect(get_console_content(console_log)).toMatchSnapshot();
70 | });
71 |
72 | it('should throw error if differences exist with --check', () => {
73 | expect(() =>
74 | run_cli('example.ts', ['--check', '--rename', 'empty.ts']),
75 | ).toThrowErrorMatchingSnapshot();
76 | });
77 |
78 | it('should not throw error if there is no difference with --check', () => {
79 | expect(() =>
80 | run_cli('example.ts', ['--check', '--rename', 'remapped.ts']),
81 | ).not.toThrow();
82 | });
83 |
84 | it('should throw error with differences if differences exist with --check and --listDifferent', () => {
85 | expect(() =>
86 | run_cli('example.ts', [
87 | '--check',
88 | '--listDifferent',
89 | '--rename',
90 | 'empty.ts',
91 | ]),
92 | ).toThrowErrorMatchingSnapshot();
93 | });
94 |
95 | it('should throw error if there is no input', () => {
96 | expect(() => remap_cli(remap_cli_parser([]))).toThrow();
97 | expect(get_console_content(console_error)).toMatchSnapshot();
98 | });
99 |
100 | it('should throw error if input and output file are the same', () => {
101 | expect(() => run_cli('example.ts', [])).toThrowErrorMatchingSnapshot();
102 | });
103 |
104 | it('should log typescript information', () => {
105 | run_cli('example.ts', ['--rename', '{{basename}}.snap.{{extname}}']);
106 | expect(get_console_content(console_log)).toMatchSnapshot();
107 | });
108 |
109 | function run_cli(test_glob: string, args: string[]) {
110 | const filename = get_fixture_filename(`remap-cli/${test_glob}`);
111 | remap_cli(remap_cli_parser([filename, ...args]));
112 | }
113 |
114 | function get_console_content(console_fn: jest.SpyInstance) {
115 | return console_fn.mock.calls.map(x => x[0]).join('\n');
116 | }
117 |
--------------------------------------------------------------------------------
/src/remap-cli.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import globby = require('globby');
3 | import intersection = require('lodash.intersection');
4 | import make_dir = require('make-dir');
5 | import * as path from 'path';
6 | import { remap } from './remap';
7 | import { create_message } from './utils/create-message';
8 | import { create_typescript_info } from './utils/create-typescript-info';
9 | import { load_typescript } from './utils/load-typescript';
10 |
11 | export interface RemapCliOptions {
12 | check?: boolean;
13 | listDifferent?: boolean;
14 | outDir?: string;
15 | rename?: string;
16 | typescript?: string;
17 | }
18 |
19 | export interface RemapCliArguments extends RemapCliOptions {
20 | _: string[];
21 | }
22 |
23 | export const remap_cli = (args: RemapCliArguments): void => {
24 | // istanbul ignore next
25 | const {
26 | _: globs,
27 | check = false,
28 | listDifferent: list_different = false,
29 | outDir: output_directory,
30 | rename: rename_template,
31 | typescript: typescript_id,
32 | } = args;
33 |
34 | const source_filenames = globby.sync(globs, { absolute: true });
35 | const logger = create_logger();
36 |
37 | if (source_filenames.length === 0) {
38 | logger.log('There is no matched file.');
39 | return;
40 | }
41 |
42 | const invalid_filenames = get_invalid_filenames(
43 | source_filenames,
44 | output_directory,
45 | rename_template,
46 | );
47 |
48 | if (invalid_filenames.length !== 0) {
49 | throw new Error(
50 | create_message(
51 | 'Input and output filename cannot be the same:',
52 | invalid_filenames,
53 | 'Consider adjusting option `--outDir` or `--rename` to redirect output',
54 | ),
55 | );
56 | }
57 |
58 | if (!check && output_directory !== undefined) {
59 | make_dir.sync(output_directory);
60 | }
61 |
62 | const { typescript: ts, typescript_path } = load_typescript(typescript_id);
63 |
64 | logger.log(create_typescript_info(ts.version, typescript_path));
65 |
66 | interface Different {
67 | source_filename: string;
68 | output_filename: string;
69 | }
70 | const differents: Different[] = [];
71 |
72 | source_filenames.forEach(source_filename => {
73 | const source_content = fs.readFileSync(source_filename, 'utf8');
74 |
75 | const snapshot_filename = get_snapshot_filename(source_filename);
76 | const snapshot_content = fs.readFileSync(snapshot_filename, 'utf8');
77 |
78 | const output_filename = get_output_filename(
79 | source_filename,
80 | output_directory,
81 | rename_template,
82 | );
83 | const output_content = remap(source_content, snapshot_content, {
84 | typescript: ts,
85 | source_filename,
86 | });
87 |
88 | if (check || list_different) {
89 | let target_content: string | undefined;
90 |
91 | try {
92 | target_content = fs.readFileSync(output_filename, 'utf8');
93 | } catch (e) {
94 | target_content = undefined;
95 | }
96 |
97 | if (output_content !== target_content) {
98 | differents.push({ source_filename, output_filename });
99 | }
100 | }
101 |
102 | if (!check) {
103 | fs.writeFileSync(output_filename, output_content);
104 | }
105 | });
106 |
107 | if (differents.length === 0) {
108 | return;
109 | }
110 |
111 | const erorr_message = 'Difference(s) detected';
112 |
113 | if (check && !list_different) {
114 | throw new Error(erorr_message);
115 | }
116 |
117 | const message = create_message(
118 | 'Listing files that their target content is different from output content:',
119 | differents.map(
120 | different =>
121 | `${different.source_filename} -> ${different.output_filename}`,
122 | ),
123 | check ? erorr_message : undefined,
124 | );
125 |
126 | if (check) {
127 | throw new Error(message);
128 | }
129 |
130 | logger.log(message);
131 | };
132 |
133 | function get_invalid_filenames(
134 | filenames: string[],
135 | output_directory?: string,
136 | rename_template?: string,
137 | ) {
138 | return intersection(
139 | filenames,
140 | filenames.map(filepath =>
141 | get_output_filename(filepath, output_directory, rename_template),
142 | ),
143 | );
144 | }
145 |
146 | function get_output_filename(
147 | filename: string,
148 | output_directory?: string,
149 | rename_template?: string,
150 | ) {
151 | const redirect_dirpath =
152 | output_directory === undefined
153 | ? path.dirname(filename)
154 | : path.resolve(output_directory);
155 |
156 | const renamed_filename = rename_filename(filename, rename_template);
157 |
158 | return path.join(redirect_dirpath, renamed_filename);
159 | }
160 |
161 | function rename_filename(filename: string, template?: string) {
162 | const base_filename = path.basename(filename);
163 |
164 | if (template === undefined) {
165 | return base_filename;
166 | }
167 |
168 | const extname = path.extname(filename);
169 | const basename = path.basename(filename, extname);
170 |
171 | return template
172 | .replace('{{filename}}', base_filename)
173 | .replace('{{basename}}', basename)
174 | .replace('{{extname}}', extname.slice(1) /* remove leading dot */);
175 | }
176 |
177 | function get_snapshot_filename(filename: string) {
178 | return path.join(
179 | path.dirname(filename),
180 | '__snapshots__',
181 | `${path.basename(filename)}.snap`,
182 | );
183 | }
184 |
185 | function create_logger() {
186 | return new (class {
187 | public counter = 0;
188 | public log(message: string) {
189 | if (this.counter++ !== 0) {
190 | console.log('');
191 | }
192 | console.log(message);
193 | }
194 | })();
195 | }
196 |
--------------------------------------------------------------------------------
/src/remap.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture } from './helpers/load-fixture';
3 | import { remap } from './remap';
4 | import { snapshot_assertion_message } from './utils/create-assertion-expression';
5 |
6 | it('should remap correctly', () => {
7 | expect(get_remapped('example')).toMatchSnapshot();
8 | });
9 |
10 | it('should throw error if there is unmatched snapshot', () => {
11 | expect(() => get_remapped('unmatched')).toThrowErrorMatchingSnapshot();
12 | });
13 |
14 | it('should throw error if there is non-string value in snapshot value', () => {
15 | const source_content = `
16 | // @dts-jest:snap
17 | Math.max(1, 2, 3);
18 | `;
19 | const snapshot_content = {
20 | [`Math.max(1, 2, 3) ${snapshot_assertion_message} 1`]: 123,
21 | };
22 | expect(() =>
23 | remap(source_content, snapshot_content),
24 | ).toThrowErrorMatchingSnapshot();
25 | });
26 |
27 | function get_remapped(id: string) {
28 | return remap(
29 | load_fixture(`remap/${id}.ts`),
30 | load_fixture(`remap/__snapshots__/${id}.ts.snap`),
31 | { typescript: ts },
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/remap.ts:
--------------------------------------------------------------------------------
1 | import * as jest_snapshot_parser from 'jest-snapshot-parser';
2 | import * as _ts from 'typescript';
3 | import { TestMethod, TriggerHeaderFlags } from './definitions';
4 | import { create_source_file } from './utils/create-source-file';
5 | import { find_triggers } from './utils/find-triggers';
6 | import { get_snapshot_description } from './utils/get-snapshot-description';
7 | import { normalize_trigger_header_methods } from './utils/normalize-trigger-header-methods';
8 | import { Parsed } from 'jest-snapshot-parser';
9 |
10 | export interface RemapOptions {
11 | typescript?: typeof _ts;
12 | source_filename?: string;
13 | }
14 |
15 | export const remap = (
16 | source_content: string,
17 | snapshot_content: string | jest_snapshot_parser.Parsed,
18 | options: RemapOptions = {},
19 | ) => {
20 | const { typescript: ts = _ts, source_filename = '' } = options;
21 |
22 | const parsed_snapshot =
23 | typeof snapshot_content === 'string'
24 | ? jest_snapshot_parser.parse(snapshot_content)
25 | : snapshot_content;
26 |
27 | const source_file = create_source_file(source_filename, source_content, ts);
28 | const triggers = find_triggers(source_file, ts);
29 |
30 | normalize_trigger_header_methods(triggers.map(trigger => trigger.header));
31 |
32 | const source_line_contents = source_content.split('\n');
33 | const counters: Record = {};
34 |
35 | triggers
36 | .filter(
37 | trigger =>
38 | trigger.header.method === TestMethod.Test &&
39 | trigger.header.flags & TriggerHeaderFlags[':snap'],
40 | )
41 | .forEach(trigger => {
42 | const description = get_snapshot_description(trigger);
43 | const counter = get_increased_counter(description);
44 | const title = `${description} ${counter}`;
45 |
46 | if (!(title in parsed_snapshot)) {
47 | throw new Error(`Unmatched snapshot title \`${title}\``);
48 | }
49 |
50 | const snapshot_value = parsed_snapshot[title];
51 |
52 | if (typeof snapshot_value !== 'string') {
53 | const snapshot_display_value = JSON.stringify(snapshot_value);
54 | throw new Error(
55 | `Snapshot value should be a string, but got ${snapshot_display_value}`,
56 | );
57 | }
58 |
59 | const snapshot_value_first_line = snapshot_value.split('\n')[0];
60 | const { header } = trigger;
61 |
62 | source_line_contents[header.line] += ` -> ${snapshot_value_first_line}`;
63 | });
64 |
65 | return source_line_contents.join('\n');
66 |
67 | function get_increased_counter(title: string) {
68 | return (counters[title] = title in counters ? counters[title] + 1 : 1);
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/src/reporter.test.ts:
--------------------------------------------------------------------------------
1 | import { cwd_serializer } from './helpers/cwd-serializer';
2 | import { version_serializer } from './helpers/version-serializer';
3 | import { Reporter } from './reporter';
4 |
5 | expect.addSnapshotSerializer(cwd_serializer);
6 | expect.addSnapshotSerializer(version_serializer);
7 |
8 | const original_stdout_write = process.stdout.write;
9 | let mocked_stdout_write: jest.MockInstance;
10 |
11 | beforeEach(() => {
12 | mocked_stdout_write = process.stdout.write = jest.fn();
13 | });
14 |
15 | afterAll(() => {
16 | process.stdout.write = original_stdout_write;
17 | });
18 |
19 | it('should report correctly', () => {
20 | const reporter = new Reporter(create_config());
21 | reporter.onRunComplete(new Set([create_context()]));
22 | expect(get_write_content()).toMatchSnapshot();
23 | });
24 |
25 | function create_context(): jest.Context {
26 | return {
27 | config: {
28 | rootDir: process.cwd(),
29 | globals: {},
30 | },
31 | } as any;
32 | }
33 |
34 | function create_config(): jest.GlobalConfig {
35 | return { useStderr: false } as any;
36 | }
37 |
38 | function get_write_content() {
39 | return mocked_stdout_write.mock.calls.map(x => x.join('')).join('');
40 | }
41 |
--------------------------------------------------------------------------------
/src/reporter.ts:
--------------------------------------------------------------------------------
1 | import * as JestTypes from '@jest/types';
2 | import * as JestReporters from '@jest/reporters';
3 | import * as path from 'path';
4 | import { config_namespace, JestConfig } from './definitions';
5 | import { create_typescript_info } from './utils/create-typescript-info';
6 | import { normalize_config } from './utils/normalize-config';
7 |
8 | export class Reporter implements JestReporters.Reporter {
9 | constructor(public global_config: JestTypes.Config.GlobalConfig) {}
10 |
11 | // istanbul ignore next
12 | public onRunStart() {}
13 |
14 | // istanbul ignore next
15 | public getLastError() {}
16 |
17 | public onRunComplete(contexts: Set) {
18 | // istanbul ignore next
19 | const write_stream = this.global_config.useStderr
20 | ? process.stderr
21 | : process.stdout;
22 |
23 | write_stream.write('\n');
24 |
25 | contexts.forEach(context => {
26 | const { typescript: ts, typescript_path } = normalize_config(
27 | (context.config as JestConfig).globals[config_namespace],
28 | );
29 |
30 | // istanbul ignore next
31 | const context_name =
32 | contexts.size > 1 ? ` ${path.basename(context.config.rootDir)}: ` : ' ';
33 |
34 | const typescript_info = create_typescript_info(
35 | ts.version,
36 | typescript_path,
37 | false,
38 | );
39 | write_stream.write(`[dts-jest]${context_name}${typescript_info}\n`);
40 | });
41 |
42 | write_stream.write('\n');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/runtime.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { cwd_serializer } from './helpers/cwd-serializer';
3 | import {
4 | get_fixture_filename,
5 | load_fixture_source_file,
6 | } from './helpers/load-fixture';
7 | import { setup } from './setup';
8 | import { find_triggers } from './utils/find-triggers';
9 |
10 | expect.addSnapshotSerializer(cwd_serializer);
11 |
12 | const runtime = get_runtime('example', false);
13 | const tranpiled_runtime = get_runtime('example', true);
14 |
15 | const pass_line = 1;
16 | const fail_line = 7;
17 | const value_line = 13;
18 |
19 | const get_description_line = (line: number) => line + 3;
20 |
21 | describe('#get_type_inference_or_diagnostic()', () => {
22 | it('should return diagnostic on fail line', () => {
23 | expect(
24 | runtime.get_type_inference_or_diagnostic(fail_line),
25 | ).toMatchSnapshot();
26 | });
27 | it('should return inference on non-fail line', () => {
28 | expect(
29 | runtime.get_type_inference_or_diagnostic(pass_line),
30 | ).toMatchSnapshot();
31 | });
32 | });
33 |
34 | describe('#get_type_inference_or_throw_diagnostic()', () => {
35 | it('should throw diagnostic on fail line', () => {
36 | expect(() =>
37 | runtime.get_type_inference_or_throw_diagnostic(fail_line),
38 | ).toThrowErrorMatchingSnapshot();
39 | });
40 | it('should return inference on non-fail line', () => {
41 | expect(
42 | runtime.get_type_inference_or_throw_diagnostic(pass_line),
43 | ).toMatchSnapshot();
44 | });
45 | });
46 |
47 | describe('#get_type_report()', () => {
48 | it('should return diagnostic report on fail line', () => {
49 | expect(runtime.get_type_report(fail_line)).toMatchSnapshot();
50 | });
51 | it('should return diagnostic report with description on fail line with description', () => {
52 | expect(
53 | runtime.get_type_report(get_description_line(fail_line)),
54 | ).toMatchSnapshot();
55 | });
56 | it('should return inference report on non-fail line', () => {
57 | expect(runtime.get_type_report(pass_line)).toMatchSnapshot();
58 | });
59 | it('should return inference report with description on non-fail line with description', () => {
60 | expect(
61 | runtime.get_type_report(get_description_line(pass_line)),
62 | ).toMatchSnapshot();
63 | });
64 | it('should return inference report on non-fail line with line info while transpiled', () => {
65 | expect(tranpiled_runtime.get_type_report(pass_line)).toMatchSnapshot();
66 | });
67 | });
68 |
69 | describe('#get_value_report()', () => {
70 | it('should return error message report for fail getter', () => {
71 | expect(
72 | runtime.get_value_report(value_line, () => {
73 | throw new Error('Example Error');
74 | }),
75 | ).toMatchSnapshot();
76 | });
77 | it('should return error message report with description on desciption line for fail getter', () => {
78 | expect(
79 | runtime.get_value_report(value_line, () => {
80 | throw new Error('Example Error');
81 | }),
82 | ).toMatchSnapshot();
83 | });
84 | it('should return formatted value report for non-fail getter', () => {
85 | expect(
86 | runtime.get_value_report(get_description_line(value_line), () => ({
87 | example: 'value',
88 | })),
89 | ).toMatchSnapshot();
90 | });
91 | it('should return formatted value report with description on desciption for non-fail getter', () => {
92 | expect(
93 | runtime.get_value_report(get_description_line(value_line), () => ({
94 | example: 'value',
95 | })),
96 | ).toMatchSnapshot();
97 | });
98 | it('should return formatted value report for non-fail getter with line info while transpiled', () => {
99 | expect(
100 | tranpiled_runtime.get_value_report(
101 | get_description_line(value_line),
102 | () => ({ example: 'value' }),
103 | ),
104 | ).toMatchSnapshot();
105 | });
106 | });
107 |
108 | function get_runtime(id: string, transpile: boolean) {
109 | const full_id = `runtime/${id}.ts`;
110 | const filename = get_fixture_filename(full_id);
111 | const source_file = load_fixture_source_file(full_id, ts);
112 | const triggers = find_triggers(source_file, ts);
113 | return setup(filename, { transpile }, triggers);
114 | }
115 |
--------------------------------------------------------------------------------
/src/runtime.ts:
--------------------------------------------------------------------------------
1 | import pretty_format = require('pretty-format');
2 | import {
3 | runtime_indent_spaces,
4 | NormalizedConfig,
5 | Snapshot,
6 | Trigger,
7 | } from './definitions';
8 | import { get_display_line } from './utils/get-display-line';
9 | import { get_trigger_body_line } from './utils/get-trigger-line';
10 | import { indent } from './utils/indent';
11 |
12 | interface RuntimeData {
13 | trigger: Trigger;
14 | snapshot?: Snapshot;
15 | }
16 |
17 | export class Runtime {
18 | private _data_map = new Map();
19 | private _filename: string;
20 | private _config: NormalizedConfig;
21 |
22 | constructor(
23 | filename: string,
24 | config: NormalizedConfig,
25 | triggers: Trigger[],
26 | snapshots: Snapshot[],
27 | ) {
28 | this._filename = filename;
29 | this._config = config;
30 | triggers.forEach(trigger => {
31 | const body_line = get_trigger_body_line(trigger.header.line);
32 | this._data_map.set(body_line, { trigger });
33 | });
34 | snapshots.forEach(snapshot => {
35 | const data = this._data_map.get(snapshot.line)!;
36 | data.snapshot = snapshot;
37 | });
38 | }
39 |
40 | public get_type_inference_or_diagnostic(body_line: number) {
41 | const data = this._data_map.get(body_line)!;
42 | const snapshot = data.snapshot!;
43 | return snapshot.diagnostic !== undefined
44 | ? snapshot.diagnostic
45 | : snapshot.inference!;
46 | }
47 |
48 | public get_type_inference_or_throw_diagnostic(body_line: number) {
49 | const data = this._data_map.get(body_line)!;
50 | const snapshot = data.snapshot!;
51 | if (snapshot.diagnostic !== undefined) {
52 | throw new Error(snapshot.diagnostic);
53 | }
54 | return snapshot.inference!;
55 | }
56 |
57 | public get_type_report(body_line: number) {
58 | const { trigger } = this._data_map.get(body_line)!;
59 |
60 | try {
61 | return this._create_report(
62 | 'type',
63 | trigger,
64 | 'Inferred',
65 | 'to be',
66 | this.get_type_inference_or_throw_diagnostic(body_line),
67 | );
68 | } catch (error) {
69 | return this._create_report(
70 | 'type',
71 | trigger,
72 | 'Inferring',
73 | 'but throw',
74 | (error as Error).message,
75 | );
76 | }
77 | }
78 |
79 | public get_value_report(body_line: number, getter: () => any) {
80 | const { trigger } = this._data_map.get(body_line)!;
81 |
82 | try {
83 | return this._create_report(
84 | 'value',
85 | trigger,
86 | 'Evaluated',
87 | 'to be',
88 | pretty_format(getter()),
89 | );
90 | } catch (error) {
91 | return this._create_report(
92 | 'value',
93 | trigger,
94 | 'Evaluating',
95 | 'but throw',
96 | (error as Error).message,
97 | );
98 | }
99 | }
100 |
101 | private _create_report(
102 | kind: 'type' | 'value',
103 | trigger: Trigger,
104 | title1: string,
105 | title2: string,
106 | value: string,
107 | ) {
108 | const { header, body } = trigger;
109 | const description =
110 | header.description === undefined ? '' : `\n${header.description}\n`;
111 |
112 | const indented_expression = indent(body.text, runtime_indent_spaces);
113 | const indented_value = indent(value, runtime_indent_spaces);
114 |
115 | const line = get_display_line(
116 | kind === 'type'
117 | ? get_trigger_body_line(header.line)
118 | : trigger.footer!.line,
119 | );
120 | const line_info = this._config.transpile
121 | ? `\n(${this._filename}:${line})\n`
122 | : '';
123 |
124 | return `${line_info}${description}\n${title1}\n\n${indented_expression}\n\n${title2}\n\n${indented_value}\n`;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/setup.ts:
--------------------------------------------------------------------------------
1 | import { RawConfig, Trigger } from './definitions';
2 | import { Runtime } from './runtime';
3 | import { create_snapshots } from './utils/create-snapshots';
4 | import { normalize_config } from './utils/normalize-config';
5 |
6 | export const setup = (
7 | filename: string,
8 | raw_config: RawConfig,
9 | triggers: Trigger[],
10 | ) => {
11 | const normalized_config = normalize_config(raw_config);
12 | const snapshots = create_snapshots(filename, triggers, normalized_config);
13 |
14 | return new Runtime(filename, normalized_config, triggers, snapshots);
15 | };
16 |
--------------------------------------------------------------------------------
/src/transform.test.ts:
--------------------------------------------------------------------------------
1 | jest.mock('./utils/create-setup-expression.ts', () => ({
2 | create_setup_expression: () => '',
3 | }));
4 |
5 | import { JestConfig, RawConfig } from './definitions';
6 | import { get_fixture_filename, load_fixture } from './helpers/load-fixture';
7 | import { transform } from './transform';
8 |
9 | it('should transform correctly with { test_type: false, test_value: false }', () => {
10 | expect(
11 | transform_fixture('all', { test_type: false, test_value: false }),
12 | ).toMatchSnapshot();
13 | });
14 |
15 | it('should transform correctly with { test_type: true, test_value: false }', () => {
16 | expect(
17 | transform_fixture('all', { test_type: true, test_value: false }),
18 | ).toMatchSnapshot();
19 | });
20 |
21 | it('should transform correctly with { test_type: false, test_value: true }', () => {
22 | expect(
23 | transform_fixture('all', { test_type: false, test_value: true }),
24 | ).toMatchSnapshot();
25 | });
26 |
27 | it('should transform correctly with { test_type: true, test_value: true }', () => {
28 | expect(
29 | transform_fixture('all', { test_type: true, test_value: true }),
30 | ).toMatchSnapshot();
31 | });
32 |
33 | it('should transform to fake environment for no-footers even if test_value = true', () => {
34 | expect(
35 | transform_fixture('no-footers', { test_type: true, test_value: true }),
36 | ).toMatchSnapshot();
37 | });
38 |
39 | it('should respect docblock options', () => {
40 | expect(
41 | transform_fixture('all', { test_type: false, test_value: false }),
42 | ).toEqual(
43 | transform_fixture(
44 | 'all',
45 | { test_type: true, test_value: true },
46 | x => `/** @dts-jest disable:test-type disable:test-value */${x}`,
47 | ),
48 | );
49 | });
50 |
51 | it('should retain line number while transforming', () => {
52 | expect(
53 | transform_fixture('multi-line', { test_value: true }),
54 | ).toMatchSnapshot();
55 | });
56 |
57 | it('should transform to commonjs if set', () => {
58 | expect(
59 | transform_fixture('commonjs', {
60 | test_value: true,
61 | transpile: true,
62 | compiler_options: { module: 'commonjs' },
63 | }),
64 | ).toMatchSnapshot();
65 | });
66 |
67 | function transform_fixture(
68 | id: string,
69 | raw_config: RawConfig,
70 | preprocessor = (x: string) => x,
71 | ) {
72 | const full_id = `transform/${id}.ts`;
73 | const filename = get_fixture_filename(full_id);
74 | const source = preprocessor(load_fixture(full_id));
75 | const config: JestConfig = {
76 | rootDir: '',
77 | globals: { _dts_jest_: { transpile: false, ...raw_config } },
78 | };
79 | return transform(source, filename, { config } as any).code;
80 | }
81 |
--------------------------------------------------------------------------------
/src/transform.ts:
--------------------------------------------------------------------------------
1 | import * as JestTransform from '@jest/transform';
2 | import {
3 | config_namespace,
4 | runtime_namespace,
5 | JestConfig,
6 | Trigger,
7 | } from './definitions';
8 | import { apply_grouping } from './utils/apply-grouping';
9 | import { create_message } from './utils/create-message';
10 | import { create_setup_expression } from './utils/create-setup-expression';
11 | import { create_source_file } from './utils/create-source-file';
12 | import { create_test_expression } from './utils/create-test-expression';
13 | import { find_docblock_options } from './utils/find-docblock-options';
14 | import { find_triggers } from './utils/find-triggers';
15 | import { get_diagnostic_message } from './utils/get-diagnostic-message';
16 | import { get_trigger_groups } from './utils/get-trigger-groups';
17 | import { get_trigger_body_line } from './utils/get-trigger-line';
18 | import { normalize_config } from './utils/normalize-config';
19 |
20 | export const transform = (
21 | source_text: string,
22 | source_filename: string,
23 | { config: jest_config }: { config: JestConfig },
24 | ): ReturnType => {
25 | const normalized_config = normalize_config(
26 | jest_config.globals[config_namespace],
27 | );
28 |
29 | const { typescript: ts, compiler_options, transpile } = normalized_config;
30 |
31 | const source_file = create_source_file(source_filename, source_text, ts);
32 | const triggers = find_triggers(source_file, ts);
33 | const groups = get_trigger_groups(triggers.map(trigger => trigger.header));
34 |
35 | const docblock_options = find_docblock_options(source_file, ts);
36 | const { test_type = normalized_config.test_type } = docblock_options;
37 | let { test_value = normalized_config.test_value } = docblock_options;
38 |
39 | if (triggers.every(trigger => trigger.footer === undefined)) {
40 | test_value = false;
41 | }
42 |
43 | const is_fake_environment = !test_value;
44 |
45 | const setup_expression = create_setup_expression(triggers);
46 | return {
47 | code: `${setup_expression};${
48 | is_fake_environment
49 | ? get_fake_environment_transformed_content()
50 | : get_real_environment_transformed_content()
51 | }`,
52 | };
53 |
54 | function get_fake_environment_transformed_content() {
55 | const transformed_line_contents = source_text.split('\n').map(() => '');
56 |
57 | triggers.forEach(trigger => {
58 | const test_expression = get_test_expression(
59 | trigger,
60 | test_type,
61 | test_value,
62 | );
63 |
64 | const body_line = get_trigger_body_line(trigger.header.line);
65 |
66 | transformed_line_contents[body_line] += test_expression;
67 | });
68 |
69 | return apply_grouping(transformed_line_contents, groups).join('\n');
70 | }
71 |
72 | function get_real_environment_transformed_content() {
73 | let transformed = source_text;
74 |
75 | for (let i = triggers.length - 1; i >= 0; i--) {
76 | const trigger = triggers[i];
77 |
78 | const { start, end, text } = trigger.body;
79 | const test_expression =
80 | get_test_expression(trigger, test_type, test_value) +
81 | text.replace(/[^\n]/g, ''); // add missing line break so as to retain line number
82 |
83 | transformed =
84 | transformed.slice(0, start) + test_expression + transformed.slice(end);
85 | }
86 |
87 | transformed = apply_grouping(transformed.split('\n'), groups).join('\n');
88 |
89 | if (!transpile) {
90 | return transformed;
91 | }
92 |
93 | const transpile_output = ts.transpileModule(transformed, {
94 | compilerOptions: compiler_options,
95 | fileName: source_filename,
96 | });
97 |
98 | // istanbul ignore next
99 | if (
100 | transpile_output.diagnostics !== undefined &&
101 | transpile_output.diagnostics.length !== 0
102 | ) {
103 | throw new Error(
104 | create_message(
105 | `Unexpected error while transpiling '${source_filename}':`,
106 | transpile_output.diagnostics.map(get_diagnostic_message),
107 | ),
108 | );
109 | }
110 |
111 | return transpile_output.outputText;
112 | }
113 | };
114 |
115 | function get_test_expression(
116 | trigger: Trigger,
117 | test_type: boolean,
118 | test_value: boolean,
119 | ) {
120 | const body_line = get_trigger_body_line(trigger.header.line);
121 | const {
122 | body: { text },
123 | } = trigger;
124 |
125 | return create_test_expression(trigger, {
126 | test_type,
127 | test_value,
128 | get_type_inference_or_diagnostic_expression: `${runtime_namespace}.get_type_inference_or_diagnostic(${body_line})`,
129 | get_type_inference_or_throw_diagnostic_expression: `${runtime_namespace}.get_type_inference_or_throw_diagnostic(${body_line})`,
130 | get_type_report_expression: `${runtime_namespace}.get_type_report(${body_line})`,
131 | get_value_report_expression: `${runtime_namespace}.get_value_report(${body_line}, function () { return ${text} })`,
132 | });
133 | }
134 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/apply-grouping.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly with groups (length = 0) 1`] = `
4 | Array [
5 | "/* line 1 */",
6 | "/* line 2 */",
7 | "/* line 3 */",
8 | ]
9 | `;
10 |
11 | exports[`should return correctly with groups (length = 1) 1`] = `
12 | Array [
13 | "/* line 1 */",
14 | "describe(\\"group L2\\", function () { /* line 2 */",
15 | "/* line 3 */ });",
16 | ]
17 | `;
18 |
19 | exports[`should return correctly with groups (length > 1) 1`] = `
20 | Array [
21 | "/* line 1 */",
22 | "/* line 2 */",
23 | "describe(\\"group L3\\", function () { /* line 3 */",
24 | "/* line 4 */",
25 | " });describe(\\"group L5\\", function () { /* line 5 */",
26 | "/* line 6 */",
27 | "/* line 7 */",
28 | " });describe(\\"group L8\\", function () { /* line 8 */",
29 | "/* line 9 */ });",
30 | ]
31 | `;
32 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/create-assertion-expression.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | Array [
5 | Array [
6 | "Math.max(1, 2, 3)",
7 | Array [
8 | "test(\\"(type) should show report\\", function () { console.log() })",
9 | "test(\\"(type) should not throw error\\", function () { expect(function () { }).not.toThrow() })",
10 | "test(\\"(type) should match snapshot\\", function () { expect().toMatchSnapshot() })",
11 | "test(\\"(value) should equal to 3\\", function () { expect(Math.max(1, 2, 3)).toEqual(3) })",
12 | ],
13 | ],
14 | Array [
15 | "Math.abs(-1)",
16 | Array [
17 | "test(\\"(type) should throw error\\", function () { expect(function () { }).toThrow() })",
18 | "test(\\"(value) should show report\\", function () { console.log() })",
19 | ],
20 | ],
21 | Array [
22 | "Math.cos(Math.PI)",
23 | Array [
24 | "test(\\"(value) should not throw error\\", function () { expect(function () { Math.cos(Math.PI) }).not.toThrow() })",
25 | ],
26 | ],
27 | Array [
28 | "Math.tan(Math.PI)",
29 | Array [
30 | "test(\\"(value) should throw error\\", function () { expect(function () { Math.tan(Math.PI) }).toThrow() })",
31 | ],
32 | ],
33 | Array [
34 | "Math.sin(Math.PI)",
35 | Array [
36 | "test(\\"(type) should show report\\", function () { console.log() })",
37 | "test(\\"(type) should not throw error\\", function () { expect(function () { }).not.toThrow() })",
38 | "test(\\"(type) should match snapshot\\", function () { expect().toMatchSnapshot() })",
39 | "test(\\"(value) should show report\\", function () { console.log() })",
40 | ],
41 | ],
42 | Array [
43 | "Object.assign({ a: 1 }, { b: 2 }, { c: 3 })",
44 | Array [
45 | "test(\\"(type) should match snapshot\\", function () { expect().toMatchSnapshot() })",
46 | "test(\\"(value) should equal to { a: 1, b: 2, c: 3, }\\", function () { expect(Object.assign({ a: 1 }, { b: 2 }, { c: 3 })).toEqual({ a: 1, b: 2, c: 3, }) })",
47 | ],
48 | ],
49 | Array [
50 | "Math.min(3, 2, 1)",
51 | Array [],
52 | ],
53 | Array [
54 | "Math.min(3, 2, 1)",
55 | Array [
56 | "test(\\"(type) should not be any\\", function () { expect().not.toBe(\\"any\\") })",
57 | ],
58 | ],
59 | ]
60 | `;
61 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/create-message.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | "my description
5 |
6 | data 1
7 | data 2
8 | data 3"
9 | `;
10 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/create-setup-expression.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `"var _dts_jest_runtime_ = require(\\"dts-jest\\").setup(module.filename, (function () { try { return _dts_jest_ } catch (e) { return {} } })(), [{\\"header\\":{\\"line\\":0,\\"flags\\":7,\\"method\\":\\"test\\"},\\"body\\":{\\"start\\":28,\\"end\\":46,\\"experssion\\":\\"Math.max(1, 2, 3)\\",\\"text\\":\\"Math.max(1, 2, 3)\\"},\\"footer\\":{\\"line\\":1,\\"expected\\":\\"3\\"}},{\\"header\\":{\\"line\\":3,\\"flags\\":8,\\"method\\":\\"test\\"},\\"body\\":{\\"start\\":73,\\"end\\":86,\\"experssion\\":\\"Math.abs(-1)\\",\\"text\\":\\"Math.abs(-1)\\"},\\"footer\\":{\\"line\\":4,\\"flag\\":\\"?\\"}}])"`;
4 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/create-snapshots.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should create correctly 1`] = `
4 | Array [
5 | Object {
6 | "diagnostic": "Argument of type 'string' is not assignable to parameter of type 'number'.",
7 | "line": 7,
8 | },
9 | Object {
10 | "diagnostic": "Argument of type '{ c: number; }' is not assignable to parameter of type '{ a: 1; }'.
11 | Object literal may only specify known properties, and 'c' does not exist in type '{ a: 1; }'.",
12 | "line": 10,
13 | },
14 | Object {
15 | "inference": "number",
16 | "line": 1,
17 | },
18 | Object {
19 | "inference": "number",
20 | "line": 4,
21 | },
22 | ]
23 | `;
24 |
25 | exports[`should support @ts-expect-error 1`] = `
26 | Array [
27 | Object {
28 | "diagnostic": "Argument of type 'string' is not assignable to parameter of type 'number'.",
29 | "line": 1,
30 | },
31 | ]
32 | `;
33 |
34 | exports[`should throw error of there are some unmatched diagnostics 1`] = `
35 | "Unmatched diagnostic(s) detected:
36 |
37 | /fixtures/create-snapshots/unmatched.ts:1 Argument of type 'string' is not assignable to parameter of type 'number'."
38 | `;
39 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/find-docblock-options.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should find disable:test-type correctly 1`] = `
4 | Object {
5 | "test_type": false,
6 | }
7 | `;
8 |
9 | exports[`should find disable:test-value correctly 1`] = `
10 | Object {
11 | "test_value": false,
12 | }
13 | `;
14 |
15 | exports[`should find enable:test-type correctly 1`] = `
16 | Object {
17 | "test_type": true,
18 | }
19 | `;
20 |
21 | exports[`should find enable:test-value correctly 1`] = `
22 | Object {
23 | "test_value": true,
24 | }
25 | `;
26 |
27 | exports[`should find multi option value correctly 1`] = `
28 | Object {
29 | "test_type": false,
30 | "test_value": true,
31 | }
32 | `;
33 |
34 | exports[`should throw error if there is unexpected value 1`] = `"Unexpected option value 'enable:nothing'"`;
35 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/find-trigger-bodies.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | Array [
5 | Object {
6 | "end": 31,
7 | "experssion": "Math.max(1, 2, 3)",
8 | "start": 13,
9 | "text": "Math.max(1, 2, 3)",
10 | },
11 | Object {
12 | "end": 73,
13 | "experssion": "Math.max(1, 2, 3)",
14 | "start": 46,
15 | "text": "Math.max(
16 | 1,
17 | 2,
18 | 3,
19 | )",
20 | },
21 | Object {
22 | "end": 119,
23 | "experssion": "Math.max(1, 2, 3)",
24 | "start": 101,
25 | "text": "Math.max(1, 2, 3)",
26 | },
27 | Object {
28 | "end": 187,
29 | "experssion": "Math.max(1, 2, 3)",
30 | "start": 152,
31 | "text": "Math.max(
32 | 1,
33 | 2,
34 | 3,
35 | )",
36 | },
37 | ]
38 | `;
39 |
40 | exports[`should throw error if there are some invalid groups 1`] = `
41 | "Invalid trigger-group(s) detected:
42 |
43 | find-trigger-bodies/invalid-group.ts:2
44 | find-trigger-bodies/invalid-group.ts:14 inside"
45 | `;
46 |
47 | exports[`should throw error if there are some unattachable bodies 1`] = `
48 | "Unattachable trigger(s) detected:
49 |
50 | find-trigger-bodies/unattachable.ts:1
51 | find-trigger-bodies/unattachable.ts:2 description"
52 | `;
53 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/find-trigger-footers.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | Array [
5 | Object {
6 | "expected": "3",
7 | "line": 1,
8 | },
9 | Object {
10 | "flag": "?",
11 | "line": 4,
12 | },
13 | Object {
14 | "flag": ":error",
15 | "line": 7,
16 | },
17 | Object {
18 | "flag": ":no-error",
19 | "line": 10,
20 | },
21 | Object {
22 | "expected": "3",
23 | "line": 13,
24 | },
25 | Object {
26 | "flag": "?",
27 | "line": 16,
28 | },
29 | Object {
30 | "flag": ":error",
31 | "line": 19,
32 | },
33 | Object {
34 | "flag": ":no-error",
35 | "line": 22,
36 | },
37 | ]
38 | `;
39 |
40 | exports[`should throw error if there are some unattachable footers 1`] = `
41 | "Unattachable trigger-footer(s) detected:
42 |
43 | find-trigger-footers/unattachable.ts:1"
44 | `;
45 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/find-trigger-headers.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | Array [
5 | Object {
6 | "description": undefined,
7 | "flag_literals": Array [],
8 | "flags": 0,
9 | "group": undefined,
10 | "line": 0,
11 | "method": "test",
12 | },
13 | Object {
14 | "description": undefined,
15 | "flag_literals": Array [
16 | ":show",
17 | ],
18 | "flags": 2,
19 | "group": undefined,
20 | "line": 1,
21 | "method": "test",
22 | },
23 | Object {
24 | "description": undefined,
25 | "flag_literals": Array [
26 | ":snap",
27 | ],
28 | "flags": 1,
29 | "group": undefined,
30 | "line": 2,
31 | "method": "test",
32 | },
33 | Object {
34 | "description": undefined,
35 | "flag_literals": Array [
36 | ":snap",
37 | ":show",
38 | ],
39 | "flags": 3,
40 | "group": undefined,
41 | "line": 3,
42 | "method": "test",
43 | },
44 | Object {
45 | "description": undefined,
46 | "flag_literals": Array [
47 | ":pass",
48 | ],
49 | "flags": 4,
50 | "group": undefined,
51 | "line": 4,
52 | "method": "test",
53 | },
54 | Object {
55 | "description": undefined,
56 | "flag_literals": Array [
57 | ":show",
58 | ":pass",
59 | ],
60 | "flags": 6,
61 | "group": undefined,
62 | "line": 5,
63 | "method": "test",
64 | },
65 | Object {
66 | "description": undefined,
67 | "flag_literals": Array [
68 | ":snap",
69 | ":pass",
70 | ],
71 | "flags": 5,
72 | "group": undefined,
73 | "line": 6,
74 | "method": "test",
75 | },
76 | Object {
77 | "description": undefined,
78 | "flag_literals": Array [
79 | ":snap",
80 | ":show",
81 | ":pass",
82 | ],
83 | "flags": 7,
84 | "group": undefined,
85 | "line": 7,
86 | "method": "test",
87 | },
88 | Object {
89 | "description": undefined,
90 | "flag_literals": Array [
91 | ":fail",
92 | ],
93 | "flags": 8,
94 | "group": undefined,
95 | "line": 8,
96 | "method": "test",
97 | },
98 | Object {
99 | "description": undefined,
100 | "flag_literals": Array [
101 | ":show",
102 | ":fail",
103 | ],
104 | "flags": 10,
105 | "group": undefined,
106 | "line": 9,
107 | "method": "test",
108 | },
109 | Object {
110 | "description": undefined,
111 | "flag_literals": Array [
112 | ":snap",
113 | ":fail",
114 | ],
115 | "flags": 9,
116 | "group": undefined,
117 | "line": 10,
118 | "method": "test",
119 | },
120 | Object {
121 | "description": undefined,
122 | "flag_literals": Array [
123 | ":snap",
124 | ":show",
125 | ":fail",
126 | ],
127 | "flags": 11,
128 | "group": undefined,
129 | "line": 11,
130 | "method": "test",
131 | },
132 | Object {
133 | "description": "description",
134 | "flag_literals": Array [],
135 | "flags": 0,
136 | "group": undefined,
137 | "line": 13,
138 | "method": "test",
139 | },
140 | Object {
141 | "description": "description + flag",
142 | "flag_literals": Array [
143 | ":pass",
144 | ],
145 | "flags": 4,
146 | "group": undefined,
147 | "line": 14,
148 | "method": "test",
149 | },
150 | ]
151 | `;
152 |
153 | exports[`should return correctly with group 1`] = `
154 | Array [
155 | Object {
156 | "description": "1",
157 | "flag_literals": Array [],
158 | "flags": 0,
159 | "group": undefined,
160 | "line": 0,
161 | "method": "test",
162 | },
163 | Object {
164 | "description": "2",
165 | "flag_literals": Array [],
166 | "flags": 0,
167 | "group": undefined,
168 | "line": 1,
169 | "method": "test",
170 | },
171 | Object {
172 | "description": "? 1",
173 | "flag_literals": Array [],
174 | "flags": 0,
175 | "group": Object {
176 | "description": undefined,
177 | "line": 2,
178 | "method": "describe",
179 | },
180 | "line": 3,
181 | "method": "test",
182 | },
183 | Object {
184 | "description": "? 2",
185 | "flag_literals": Array [],
186 | "flags": 0,
187 | "group": Object {
188 | "description": undefined,
189 | "line": 2,
190 | "method": "describe",
191 | },
192 | "line": 4,
193 | "method": "test",
194 | },
195 | Object {
196 | "description": "A 1",
197 | "flag_literals": Array [],
198 | "flags": 0,
199 | "group": Object {
200 | "description": "A",
201 | "line": 5,
202 | "method": "describe",
203 | },
204 | "line": 6,
205 | "method": "test",
206 | },
207 | Object {
208 | "description": "A 2",
209 | "flag_literals": Array [],
210 | "flags": 0,
211 | "group": Object {
212 | "description": "A",
213 | "line": 5,
214 | "method": "describe",
215 | },
216 | "line": 7,
217 | "method": "test",
218 | },
219 | Object {
220 | "description": "B 1",
221 | "flag_literals": Array [],
222 | "flags": 0,
223 | "group": Object {
224 | "description": "B",
225 | "line": 8,
226 | "method": "describe",
227 | },
228 | "line": 9,
229 | "method": "test",
230 | },
231 | Object {
232 | "description": "B 2",
233 | "flag_literals": Array [],
234 | "flags": 0,
235 | "group": Object {
236 | "description": "B",
237 | "line": 8,
238 | "method": "describe",
239 | },
240 | "line": 10,
241 | "method": "test",
242 | },
243 | ]
244 | `;
245 |
246 | exports[`should return correctly with method 1`] = `
247 | Array [
248 | Object {
249 | "description": undefined,
250 | "flag_literals": Array [],
251 | "flags": 0,
252 | "group": undefined,
253 | "line": 0,
254 | "method": "test",
255 | },
256 | Object {
257 | "description": undefined,
258 | "flag_literals": Array [
259 | ":only",
260 | ],
261 | "flags": 16,
262 | "group": undefined,
263 | "line": 1,
264 | "method": "test.only",
265 | },
266 | Object {
267 | "description": undefined,
268 | "flag_literals": Array [
269 | ":skip",
270 | ],
271 | "flags": 32,
272 | "group": undefined,
273 | "line": 2,
274 | "method": "test.skip",
275 | },
276 | Object {
277 | "description": undefined,
278 | "flag_literals": Array [],
279 | "flags": 0,
280 | "group": Object {
281 | "description": undefined,
282 | "line": 4,
283 | "method": "describe",
284 | },
285 | "line": 6,
286 | "method": "test",
287 | },
288 | Object {
289 | "description": undefined,
290 | "flag_literals": Array [
291 | ":only",
292 | ],
293 | "flags": 16,
294 | "group": Object {
295 | "description": undefined,
296 | "line": 4,
297 | "method": "describe",
298 | },
299 | "line": 7,
300 | "method": "test.only",
301 | },
302 | Object {
303 | "description": undefined,
304 | "flag_literals": Array [
305 | ":skip",
306 | ],
307 | "flags": 32,
308 | "group": Object {
309 | "description": undefined,
310 | "line": 4,
311 | "method": "describe",
312 | },
313 | "line": 8,
314 | "method": "test.skip",
315 | },
316 | Object {
317 | "description": undefined,
318 | "flag_literals": Array [],
319 | "flags": 0,
320 | "group": Object {
321 | "description": undefined,
322 | "line": 10,
323 | "method": "describe.only",
324 | },
325 | "line": 12,
326 | "method": "test",
327 | },
328 | Object {
329 | "description": undefined,
330 | "flag_literals": Array [
331 | ":only",
332 | ],
333 | "flags": 16,
334 | "group": Object {
335 | "description": undefined,
336 | "line": 10,
337 | "method": "describe.only",
338 | },
339 | "line": 13,
340 | "method": "test.only",
341 | },
342 | Object {
343 | "description": undefined,
344 | "flag_literals": Array [
345 | ":skip",
346 | ],
347 | "flags": 32,
348 | "group": Object {
349 | "description": undefined,
350 | "line": 10,
351 | "method": "describe.only",
352 | },
353 | "line": 14,
354 | "method": "test.skip",
355 | },
356 | Object {
357 | "description": undefined,
358 | "flag_literals": Array [],
359 | "flags": 0,
360 | "group": Object {
361 | "description": undefined,
362 | "line": 16,
363 | "method": "describe.skip",
364 | },
365 | "line": 17,
366 | "method": "test",
367 | },
368 | Object {
369 | "description": undefined,
370 | "flag_literals": Array [
371 | ":only",
372 | ],
373 | "flags": 16,
374 | "group": Object {
375 | "description": undefined,
376 | "line": 16,
377 | "method": "describe.skip",
378 | },
379 | "line": 18,
380 | "method": "test.only",
381 | },
382 | Object {
383 | "description": undefined,
384 | "flag_literals": Array [
385 | ":skip",
386 | ],
387 | "flags": 32,
388 | "group": Object {
389 | "description": undefined,
390 | "line": 16,
391 | "method": "describe.skip",
392 | },
393 | "line": 19,
394 | "method": "test.skip",
395 | },
396 | ]
397 | `;
398 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/find-triggers.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | Array [
5 | Object {
6 | "body": Object {
7 | "end": 63,
8 | "experssion": "Math.max(1, 2, 3)",
9 | "start": 45,
10 | "text": "Math.max(1, 2, 3)",
11 | },
12 | "footer": Object {
13 | "expected": "3",
14 | "line": 3,
15 | },
16 | "header": Object {
17 | "description": undefined,
18 | "flag_literals": Array [
19 | ":snap",
20 | ":pass",
21 | ],
22 | "flags": 5,
23 | "group": Object {
24 | "description": "A",
25 | "line": 0,
26 | "method": "describe",
27 | },
28 | "line": 2,
29 | "method": "test",
30 | },
31 | },
32 | Object {
33 | "body": Object {
34 | "end": 103,
35 | "experssion": "Math.abs(-1)",
36 | "start": 90,
37 | "text": "Math.abs(-1)",
38 | },
39 | "footer": Object {
40 | "flag": "?",
41 | "line": 6,
42 | },
43 | "header": Object {
44 | "description": undefined,
45 | "flag_literals": Array [
46 | ":fail",
47 | ],
48 | "flags": 8,
49 | "group": Object {
50 | "description": "A",
51 | "line": 0,
52 | "method": "describe",
53 | },
54 | "line": 5,
55 | "method": "test",
56 | },
57 | },
58 | Object {
59 | "body": Object {
60 | "end": 165,
61 | "experssion": "Math.cos(Math.PI)",
62 | "start": 147,
63 | "text": "Math.cos(Math.PI)",
64 | },
65 | "footer": undefined,
66 | "header": Object {
67 | "description": undefined,
68 | "flag_literals": Array [],
69 | "flags": 0,
70 | "group": Object {
71 | "description": "B",
72 | "line": 8,
73 | "method": "describe",
74 | },
75 | "line": 10,
76 | "method": "test",
77 | },
78 | },
79 | Object {
80 | "body": Object {
81 | "end": 224,
82 | "experssion": "Object.assign({ a: 1 }, { b: 2 }, { c: 3 })",
83 | "start": 180,
84 | "text": "Object.assign({ a: 1 }, { b: 2 }, { c: 3 })",
85 | },
86 | "footer": Object {
87 | "expected": "{ a: 1, b: 2, c: 3, }",
88 | "line": 14,
89 | },
90 | "header": Object {
91 | "description": undefined,
92 | "flag_literals": Array [],
93 | "flags": 0,
94 | "group": Object {
95 | "description": "B",
96 | "line": 8,
97 | "method": "describe",
98 | },
99 | "line": 13,
100 | "method": "test",
101 | },
102 | },
103 | ]
104 | `;
105 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/get-comment-content.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly for multi line comment 1`] = `"multi-line-comment"`;
4 |
5 | exports[`should return correctly for single line comment 1`] = `"single-line-comment"`;
6 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/indent.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly 1`] = `
4 | " line 1
5 | line 2
6 | line 3"
7 | `;
8 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/load-compiler-options.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should return correctly with tsconfig (filename) 1`] = `
4 | Object {
5 | "file_names": Array [
6 | "/fixtures/load-compiler-options/example/placeholder.ts",
7 | "/fixtures/load-compiler-options/example/types/index.d.ts",
8 | ],
9 | "options": Object {
10 | "allowSyntheticDefaultImports": true,
11 | "configFilePath": undefined,
12 | "declaration": true,
13 | "module": 1,
14 | "strict": true,
15 | "target": 2,
16 | "typeRoots": Array [
17 | "/fixtures/load-compiler-options/example/types",
18 | ],
19 | },
20 | }
21 | `;
22 |
23 | exports[`should return correctly with tsconfig (raw-options) 1`] = `
24 | Object {
25 | "file_names": Array [],
26 | "options": Object {
27 | "module": 1,
28 | "strict": true,
29 | "target": 2,
30 | },
31 | }
32 | `;
33 |
34 | exports[`should throw error if parse tsconfig failed 1`] = `
35 | "Unexpected error(s) while parsing tsconfig (/fixtures/load-compiler-options/invalid/tsconfig.json):
36 |
37 | Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2019.intl', 'es2020.bigint', 'es2020.date', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2020.number', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'es2022.array', 'es2022.error', 'es2022.intl', 'es2022.object', 'es2022.sharedmemory', 'es2022.string', 'es2022.regexp', 'es2023.array', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref', 'decorators', 'decorators.legacy'."
38 | `;
39 |
40 | exports[`should throw error if there are some invalid values 1`] = `
41 | "Unexpected error(s) while loading compiler options:
42 |
43 | Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext'.
44 | Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'."
45 | `;
46 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/normalize-expected-value.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should remove comments correctly 1`] = `"'test'"`;
4 |
5 | exports[`should remove newline correctly 1`] = `"(() => { switch (x) { case 0: return -1; default: return 1; } })()"`;
6 |
7 | exports[`should remove trailing semicolon correctly 1`] = `"123"`;
8 |
9 | exports[`should replace newline character with escaped newline correctly (NoSubstitutionTemplateLiteral) 1`] = `"\`123\\\\n456\\\\n789\`"`;
10 |
11 | exports[`should replace newline character with escaped newline correctly (TemplateHead/TemplateMiddle/TemplateTail) 1`] = `"\`12\${3}\\\\n456\\\\n\${7}89\`"`;
12 |
13 | exports[`should throw error if parsing error 1`] = `
14 | "Unexpected error(s) while parsing expected value:
15 |
16 | (somewhere.ts:0)
17 |
18 | ',' expected.
19 | ',' expected.
20 | '=>' expected."
21 | `;
22 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/normalize-trigger-header-methods.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should normalize correctly with group-only 1`] = `
4 | Array [
5 | "1 -> test.skip",
6 | "2 -> test.skip",
7 | "A1 -> test",
8 | "A2 -> test",
9 | "B1 -> test.skip",
10 | "B2 -> test.skip",
11 | ]
12 | `;
13 |
14 | exports[`should normalize correctly with multi group-only 1`] = `
15 | Array [
16 | "1 -> test.skip",
17 | "2 -> test.skip",
18 | "A1 -> test",
19 | "A2 -> test",
20 | "B1 -> test.skip",
21 | "B2 -> test.skip",
22 | "C1 -> test",
23 | "C2 -> test",
24 | ]
25 | `;
26 |
27 | exports[`should normalize correctly with multi group-only and trigger-only 1`] = `
28 | Array [
29 | "1 -> test.skip",
30 | "2 -> test",
31 | "3 -> test",
32 | "A1 -> test.skip",
33 | "A2 -> test.skip",
34 | "B1 -> test.skip",
35 | "B2 -> test.skip",
36 | "C1 -> test",
37 | "C2 -> test",
38 | ]
39 | `;
40 |
41 | exports[`should normalize correctly with multi trigger-only 1`] = `
42 | Array [
43 | "1 -> test.skip",
44 | "2 -> test",
45 | "3 -> test",
46 | "A1 -> test.skip",
47 | "A2 -> test.skip",
48 | "B1 -> test.skip",
49 | "B2 -> test.skip",
50 | ]
51 | `;
52 |
53 | exports[`should normalize correctly with trigger-only 1`] = `
54 | Array [
55 | "1 -> test.skip",
56 | "2 -> test",
57 | "A1 -> test.skip",
58 | "A2 -> test.skip",
59 | "B1 -> test.skip",
60 | "B2 -> test.skip",
61 | ]
62 | `;
63 |
64 | exports[`should normalize correctly with trigger-only in group-only 1`] = `
65 | Array [
66 | "1 -> test.skip",
67 | "2 -> test.skip",
68 | "A1 -> test",
69 | "A2 -> test.skip",
70 | "B1 -> test.skip",
71 | "B2 -> test.skip",
72 | ]
73 | `;
74 |
75 | exports[`should normalize correctly with trigger-only in group-skip 1`] = `
76 | Array [
77 | "1 -> test.skip",
78 | "2 -> test.skip",
79 | "A1 -> test",
80 | "A2 -> test.skip",
81 | "B1 -> test.skip",
82 | "B2 -> test.skip",
83 | ]
84 | `;
85 |
86 | exports[`should not be affected without only 1`] = `
87 | Array [
88 | "1 -> test",
89 | "2 -> test",
90 | "A1 -> test",
91 | "A2 -> test",
92 | "B1 -> test",
93 | "B2 -> test",
94 | ]
95 | `;
96 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/parse-trigger-header-flags.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should parse correclty 1`] = `5`;
4 |
5 | exports[`should throw error if group flag and assertion flag appear simultaneously 1`] = `"AssertionFlag is not allowed to be used with flag ':group'"`;
6 |
7 | exports[`should throw error if pass flag and fail flag appear simultaneously 1`] = `":pass and :fail cannot be used simultaneously'"`;
8 |
9 | exports[`should throw error if there is a duplicate flag 1`] = `"Duplicate flag ':fail'"`;
10 |
11 | exports[`should throw error if there is an unexpected flag 1`] = `"Unexpected flag ':unexpected'"`;
12 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/traverse-node.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should work correctly 1`] = `
4 | Array [
5 | Object {
6 | "kind": "ExpressionStatement",
7 | "text": "Math.max(1, 2, 3);",
8 | },
9 | Object {
10 | "kind": "CallExpression",
11 | "text": "Math.max(1, 2, 3)",
12 | },
13 | Object {
14 | "kind": "PropertyAccessExpression",
15 | "text": "Math.max",
16 | },
17 | Object {
18 | "kind": "Identifier",
19 | "text": "Math",
20 | },
21 | Object {
22 | "kind": "Identifier",
23 | "text": "max",
24 | },
25 | Object {
26 | "kind": "FirstLiteralToken",
27 | "text": "1",
28 | },
29 | Object {
30 | "kind": "FirstLiteralToken",
31 | "text": "2",
32 | },
33 | Object {
34 | "kind": "FirstLiteralToken",
35 | "text": "3",
36 | },
37 | Object {
38 | "kind": "FunctionDeclaration",
39 | "text": "function abc() {
40 | console.log('something');
41 | }",
42 | },
43 | Object {
44 | "kind": "Identifier",
45 | "text": "abc",
46 | },
47 | Object {
48 | "kind": "Block",
49 | "text": "{
50 | console.log('something');
51 | }",
52 | },
53 | Object {
54 | "kind": "ExpressionStatement",
55 | "text": "console.log('something');",
56 | },
57 | Object {
58 | "kind": "CallExpression",
59 | "text": "console.log('something')",
60 | },
61 | Object {
62 | "kind": "PropertyAccessExpression",
63 | "text": "console.log",
64 | },
65 | Object {
66 | "kind": "Identifier",
67 | "text": "console",
68 | },
69 | Object {
70 | "kind": "Identifier",
71 | "text": "log",
72 | },
73 | Object {
74 | "kind": "StringLiteral",
75 | "text": "'something'",
76 | },
77 | Object {
78 | "kind": "EndOfFileToken",
79 | "text": "",
80 | },
81 | ]
82 | `;
83 |
--------------------------------------------------------------------------------
/src/utils/apply-grouping.test.ts:
--------------------------------------------------------------------------------
1 | import { GroupMethod } from '../definitions';
2 | import { apply_grouping } from './apply-grouping';
3 |
4 | it('should return correctly with groups (length = 0)', () => {
5 | expect(
6 | apply_grouping(['/* line 1 */', '/* line 2 */', '/* line 3 */'], []),
7 | ).toMatchSnapshot();
8 | });
9 |
10 | it('should return correctly with groups (length = 1)', () => {
11 | expect(
12 | apply_grouping(
13 | ['/* line 1 */', '/* line 2 */', '/* line 3 */'],
14 | [{ line: 1, method: GroupMethod.Test, description: 'group L2' }],
15 | ),
16 | ).toMatchSnapshot();
17 | });
18 |
19 | it('should return correctly with groups (length > 1)', () => {
20 | expect(
21 | apply_grouping(
22 | [
23 | '/* line 1 */',
24 | '/* line 2 */',
25 | '/* line 3 */',
26 | '/* line 4 */',
27 | '/* line 5 */',
28 | '/* line 6 */',
29 | '/* line 7 */',
30 | '/* line 8 */',
31 | '/* line 9 */',
32 | ],
33 | [
34 | { line: 2, method: GroupMethod.Test, description: 'group L3' },
35 | { line: 4, method: GroupMethod.Test, description: 'group L5' },
36 | { line: 7, method: GroupMethod.Test, description: 'group L8' },
37 | ],
38 | ),
39 | ).toMatchSnapshot();
40 | });
41 |
--------------------------------------------------------------------------------
/src/utils/apply-grouping.ts:
--------------------------------------------------------------------------------
1 | import { TriggerGroup } from '../definitions';
2 | import { create_group_expression } from './create-group-expression';
3 |
4 | export const apply_grouping = (
5 | line_contents: string[],
6 | groups: TriggerGroup[],
7 | ) => {
8 | const grouped_line_contents = line_contents.slice();
9 |
10 | if (groups.length === 0) {
11 | return grouped_line_contents;
12 | }
13 |
14 | interface GroupScope {
15 | start_line: number;
16 | end_line: number;
17 | group: TriggerGroup;
18 | }
19 |
20 | const scopes: GroupScope[] = [];
21 |
22 | if (groups.length === 1) {
23 | const [group] = groups;
24 | scopes.push({
25 | group,
26 | start_line: group.line,
27 | end_line: line_contents.length - 1,
28 | });
29 | } else {
30 | let last_group = groups[0];
31 |
32 | for (let i = 1; i < groups.length; i++) {
33 | const group = groups[i];
34 |
35 | scopes.push({
36 | group: last_group,
37 | start_line: last_group.line,
38 | end_line: group.line,
39 | });
40 |
41 | last_group = group;
42 | }
43 |
44 | scopes.push({
45 | group: last_group,
46 | start_line: last_group.line,
47 | end_line: line_contents.length - 1,
48 | });
49 | }
50 |
51 | scopes
52 | .map(scope => {
53 | const [group_expression_left, group_expression_right] =
54 | `${create_group_expression(scope.group, '\n')};`.split('\n');
55 |
56 | grouped_line_contents[scope.start_line] =
57 | group_expression_left + grouped_line_contents[scope.start_line];
58 |
59 | return { scope, group_expression_right };
60 | })
61 | .forEach(({ scope, group_expression_right }, index) => {
62 | grouped_line_contents[scope.end_line] =
63 | index === scopes.length - 1
64 | ? grouped_line_contents[scope.end_line] + group_expression_right
65 | : group_expression_right + grouped_line_contents[scope.end_line];
66 | });
67 |
68 | return grouped_line_contents;
69 | };
70 |
--------------------------------------------------------------------------------
/src/utils/create-assertion-expression.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { create_assertion_expression } from './create-assertion-expression';
4 | import { find_triggers } from './find-triggers';
5 |
6 | it('should return correctly', () => {
7 | const triggers = find_triggers(
8 | load_fixture_source_file('create-assertion-expression/example.ts', ts),
9 | ts,
10 | );
11 | expect(
12 | triggers.map(trigger => [
13 | trigger.body.text,
14 | create_assertion_expression(trigger, {
15 | test_type: true,
16 | test_value: true,
17 | get_type_inference_or_diagnostic_expression:
18 | '',
19 | get_type_inference_or_throw_diagnostic_expression:
20 | '',
21 | get_type_report_expression: '',
22 | get_value_report_expression: '',
23 | })
24 | .split(';')
25 | .filter(x => x.length !== 0),
26 | ]),
27 | ).toMatchSnapshot();
28 | });
29 |
--------------------------------------------------------------------------------
/src/utils/create-assertion-expression.ts:
--------------------------------------------------------------------------------
1 | import { Trigger, TriggerFooterFlag, TriggerHeaderFlags } from '../definitions';
2 |
3 | export interface CreateAssertionExpressionOptions {
4 | test_type: boolean;
5 | test_value: boolean;
6 | get_type_inference_or_diagnostic_expression: string;
7 | get_type_inference_or_throw_diagnostic_expression: string;
8 | get_type_report_expression: string;
9 | get_value_report_expression: string;
10 | }
11 |
12 | export const snapshot_assertion_message = '(type) should match snapshot';
13 |
14 | export const create_assertion_expression = (
15 | trigger: Trigger,
16 | options: CreateAssertionExpressionOptions,
17 | ) => {
18 | const expressions: string[] = [];
19 |
20 | const { header, body, footer } = trigger;
21 |
22 | push_type_show_if_available();
23 | push_type_pass_if_available();
24 | push_type_fail_if_available();
25 | push_type_snap_if_available();
26 | push_type_not_any_if_available();
27 | push_value_show_if_available();
28 | push_value_equal_if_available();
29 | push_value_error_if_available();
30 | push_value_no_error_if_available();
31 |
32 | return expressions.join(';');
33 |
34 | function push_type_show_if_available() {
35 | if (options.test_type && header.flags & TriggerHeaderFlags[':show']) {
36 | expressions.push(
37 | create_wrapper(
38 | '(type) should show report',
39 | `console.log(${options.get_type_report_expression})`,
40 | ),
41 | );
42 | }
43 | }
44 |
45 | function push_type_pass_if_available() {
46 | if (options.test_type && header.flags & TriggerHeaderFlags[':pass']) {
47 | expressions.push(
48 | create_wrapper(
49 | '(type) should not throw error',
50 | `expect(function () { ${options.get_type_inference_or_throw_diagnostic_expression} }).not.toThrow()`,
51 | ),
52 | );
53 | }
54 | }
55 |
56 | function push_type_fail_if_available() {
57 | if (options.test_type && header.flags & TriggerHeaderFlags[':fail']) {
58 | expressions.push(
59 | create_wrapper(
60 | '(type) should throw error',
61 | `expect(function () { ${options.get_type_inference_or_throw_diagnostic_expression} }).toThrow()`,
62 | ),
63 | );
64 | }
65 | }
66 |
67 | function push_type_snap_if_available() {
68 | if (options.test_type && header.flags & TriggerHeaderFlags[':snap']) {
69 | expressions.push(
70 | create_wrapper(
71 | snapshot_assertion_message,
72 | `expect(${options.get_type_inference_or_diagnostic_expression}).toMatchSnapshot()`,
73 | ),
74 | );
75 | }
76 | }
77 |
78 | function push_type_not_any_if_available() {
79 | if (options.test_type && header.flags & TriggerHeaderFlags[':not-any']) {
80 | expressions.push(
81 | create_wrapper(
82 | '(type) should not be any',
83 | `expect(${options.get_type_inference_or_diagnostic_expression}).not.toBe("any")`,
84 | ),
85 | );
86 | }
87 | }
88 |
89 | function push_value_equal_if_available() {
90 | if (
91 | options.test_value &&
92 | footer !== undefined &&
93 | footer.flag === undefined
94 | ) {
95 | expressions.push(
96 | create_wrapper(
97 | `(value) should equal to ${footer.expected}`,
98 | `expect(${body.experssion}).toEqual(${footer.expected})`,
99 | ),
100 | );
101 | }
102 | }
103 |
104 | function push_value_error_if_available() {
105 | if (
106 | options.test_value &&
107 | footer !== undefined &&
108 | footer.flag === TriggerFooterFlag.Error
109 | ) {
110 | expressions.push(
111 | create_wrapper(
112 | '(value) should throw error',
113 | `expect(function () { ${body.experssion} }).toThrow()`,
114 | ),
115 | );
116 | }
117 | }
118 |
119 | function push_value_no_error_if_available() {
120 | if (
121 | options.test_value &&
122 | footer !== undefined &&
123 | footer.flag === TriggerFooterFlag.NoError
124 | ) {
125 | expressions.push(
126 | create_wrapper(
127 | '(value) should not throw error',
128 | `expect(function () { ${body.experssion} }).not.toThrow()`,
129 | ),
130 | );
131 | }
132 | }
133 |
134 | function push_value_show_if_available() {
135 | if (
136 | options.test_value &&
137 | footer !== undefined &&
138 | footer.flag === TriggerFooterFlag.Show
139 | ) {
140 | expressions.push(
141 | create_wrapper(
142 | '(value) should show report',
143 | `console.log(${options.get_value_report_expression})`,
144 | ),
145 | );
146 | }
147 | }
148 |
149 | function create_wrapper(title: string, expression: string) {
150 | const description = JSON.stringify(title);
151 | const {
152 | header: { method },
153 | } = trigger;
154 | return `${method}(${description}, function () { ${expression} })`;
155 | }
156 | };
157 |
--------------------------------------------------------------------------------
/src/utils/create-group-expression.ts:
--------------------------------------------------------------------------------
1 | import { TriggerGroup } from '../definitions';
2 | import { get_group_description } from './get-group-description';
3 |
4 | export const create_group_expression = (
5 | group: TriggerGroup,
6 | test_expression: string,
7 | ) => {
8 | const description = JSON.stringify(get_group_description(group));
9 | return `${group.method}(${description}, function () { ${test_expression} })`;
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/create-message.test.ts:
--------------------------------------------------------------------------------
1 | import { create_message } from './create-message';
2 |
3 | it('should return correctly', () => {
4 | expect(
5 | create_message('my description', ['data 1', 'data 2', 'data 3']),
6 | ).toMatchSnapshot();
7 | });
8 |
--------------------------------------------------------------------------------
/src/utils/create-message.ts:
--------------------------------------------------------------------------------
1 | import { indent } from './indent';
2 |
3 | export const create_message = (
4 | description: string,
5 | data: string[],
6 | footer?: string,
7 | ) => {
8 | const content = data
9 | .map(line_content => indent(line_content, 2))
10 | .join('\n')
11 | .replace(/ +$/gm, '');
12 |
13 | return footer === undefined
14 | ? `${description}\n\n${content}`
15 | : `${description}\n\n${content}\n\n${footer}`;
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/create-setup-expression.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { create_setup_expression } from './create-setup-expression';
4 | import { find_triggers } from './find-triggers';
5 |
6 | it('should return correctly', () => {
7 | const source_file = load_fixture_source_file(
8 | 'create-setup-expression/example.ts',
9 | ts,
10 | );
11 | const triggers = find_triggers(source_file, ts);
12 | expect(create_setup_expression(triggers)).toMatchSnapshot();
13 | });
14 |
--------------------------------------------------------------------------------
/src/utils/create-setup-expression.ts:
--------------------------------------------------------------------------------
1 | import {
2 | config_namespace,
3 | package_name,
4 | runtime_namespace,
5 | Trigger,
6 | } from '../definitions';
7 |
8 | export const create_setup_expression = (triggers: Trigger[]) => {
9 | const config_expression = `(function () { try { return ${config_namespace} } catch (e) { return {} } })()`;
10 | return (
11 | `var ${runtime_namespace} = require(${JSON.stringify(package_name)})` +
12 | `.setup(module.filename, ${config_expression}, ${JSON.stringify(triggers)})`
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/utils/create-snapshots.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { cwd_serializer } from '../helpers/cwd-serializer';
3 | import {
4 | get_fixture_filename,
5 | load_fixture_source_file,
6 | } from '../helpers/load-fixture';
7 | import { create_snapshots } from './create-snapshots';
8 | import { find_triggers } from './find-triggers';
9 | import { normalize_config } from './normalize-config';
10 |
11 | expect.addSnapshotSerializer(cwd_serializer);
12 |
13 | it('should create correctly', () => {
14 | expect(get_snapshots('example')).toMatchSnapshot();
15 | });
16 |
17 | it('should return empty array if there is no assertion', () => {
18 | expect(get_snapshots('no-assertion')).toHaveLength(0);
19 | });
20 |
21 | it('should throw error of there are some unmatched diagnostics', () => {
22 | expect(() => get_snapshots('unmatched')).toThrowErrorMatchingSnapshot();
23 | });
24 |
25 | it('should support @ts-expect-error', () => {
26 | expect(get_snapshots('ts-expect-error')).toMatchSnapshot();
27 | });
28 |
29 | function get_snapshots(id: string) {
30 | const full_id = `create-snapshots/${id}.ts`;
31 | const filename = get_fixture_filename(full_id);
32 | const source_file = load_fixture_source_file(full_id, ts);
33 | const triggers = find_triggers(source_file, ts);
34 | const config = normalize_config({});
35 | return create_snapshots(filename, triggers, config);
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/create-snapshots.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import {
3 | NormalizedConfig,
4 | Snapshot,
5 | Trigger,
6 | TriggerHeaderFlags,
7 | } from '../definitions';
8 | import { create_message } from './create-message';
9 | import { get_diagnostic_message } from './get-diagnostic-message';
10 | import { get_display_line } from './get-display-line';
11 | import { get_trigger_body_line } from './get-trigger-line';
12 | import { traverse_node } from './traverse-node';
13 |
14 | export const create_snapshots = (
15 | filename: string,
16 | triggers: Trigger[],
17 | normalized_config: NormalizedConfig,
18 | ) => {
19 | const {
20 | typescript: ts,
21 | compiler_options,
22 | file_names,
23 | enclosing_declaration,
24 | type_format_flags,
25 | } = normalized_config;
26 |
27 | const program = ts.createProgram([filename, ...file_names], compiler_options);
28 | const source_file = program.getSourceFile(filename)!;
29 |
30 | const body_line_map = new Map();
31 | triggers.forEach(trigger => {
32 | if (trigger.header.flags & TriggerHeaderFlags.Assertion) {
33 | const body_line = get_trigger_body_line(trigger.header.line);
34 | body_line_map.set(body_line, trigger);
35 | }
36 | });
37 |
38 | interface UnmatchedDiagnostic {
39 | line: number;
40 | message: string;
41 | }
42 | const unmatched_diagnostics: UnmatchedDiagnostic[] = [];
43 |
44 | const snapshots: Snapshot[] = [];
45 |
46 | make_ts_expect_error_diagnostics_available(source_file, ts);
47 |
48 | const diagnostics = ts.getPreEmitDiagnostics(program, source_file);
49 | for (const diagnostic of diagnostics) {
50 | const position = diagnostic.start!;
51 | const { line } = source_file.getLineAndCharacterOfPosition(position);
52 |
53 | if (!body_line_map.has(line)) {
54 | unmatched_diagnostics.push({
55 | line,
56 | message: get_diagnostic_message(diagnostic),
57 | });
58 | continue;
59 | }
60 |
61 | snapshots.push({
62 | line,
63 | diagnostic: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
64 | });
65 |
66 | body_line_map.delete(line);
67 | }
68 |
69 | if (unmatched_diagnostics.length !== 0) {
70 | throw new Error(
71 | create_message(
72 | 'Unmatched diagnostic(s) detected:',
73 | unmatched_diagnostics.map(
74 | ({ line, message }) =>
75 | `${source_file.fileName}:${get_display_line(line)} ${message}`,
76 | ),
77 | ),
78 | );
79 | }
80 |
81 | if (body_line_map.size === 0) {
82 | return snapshots;
83 | }
84 |
85 | const checker = program.getTypeChecker();
86 | traverse_node(
87 | source_file,
88 | node => {
89 | const position = node.getStart();
90 | const { line } = source_file.getLineAndCharacterOfPosition(position);
91 |
92 | if (!body_line_map.has(line)) {
93 | return;
94 | }
95 |
96 | const target_node = node.getChildAt(0);
97 | const type = checker.getTypeAtLocation(target_node);
98 |
99 | snapshots.push({
100 | line,
101 | inference: checker.typeToString(
102 | type,
103 | // istanbul ignore next
104 | enclosing_declaration ? node : undefined,
105 | type_format_flags,
106 | ),
107 | });
108 |
109 | body_line_map.delete(line);
110 | },
111 | ts,
112 | );
113 |
114 | return snapshots;
115 | };
116 |
117 | // ref: https://github.com/microsoft/TypeScript/pull/36014
118 | function make_ts_expect_error_diagnostics_available(
119 | source_file: _ts.SourceFile,
120 | ts: typeof _ts,
121 | ): void {
122 | const comment_directives: undefined | unknown[] =
123 | // @ts-expect-error: internal api
124 | source_file.commentDirectives;
125 |
126 | if (!Array.isArray(comment_directives)) {
127 | return;
128 | }
129 |
130 | for (let i = comment_directives.length - 1; i >= 0; i--) {
131 | const comment_directive = comment_directives[i];
132 | // istanbul ignore else
133 | // @ts-expect-error: internal api
134 | if (comment_directive.type === ts.CommentDirectiveType.ExpectError) {
135 | comment_directives.splice(i, 1);
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/utils/create-source-file.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | export const create_source_file = (
4 | filename: string,
5 | source: string,
6 | ts: typeof _ts,
7 | ) => ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
8 |
--------------------------------------------------------------------------------
/src/utils/create-test-expression.ts:
--------------------------------------------------------------------------------
1 | import { GroupMethod, Trigger } from '../definitions';
2 | import {
3 | create_assertion_expression,
4 | CreateAssertionExpressionOptions,
5 | } from './create-assertion-expression';
6 | import { get_description_for_jest } from './get-description-for-jest';
7 |
8 | export const create_test_expression = (
9 | trigger: Trigger,
10 | options: CreateAssertionExpressionOptions,
11 | ) => {
12 | const description = JSON.stringify(get_description_for_jest(trigger));
13 | const assertion_expression = create_assertion_expression(trigger, options);
14 | return `${GroupMethod.Test}(${description}, function () { ${assertion_expression} })`;
15 | };
16 |
--------------------------------------------------------------------------------
/src/utils/create-typescript-info.ts:
--------------------------------------------------------------------------------
1 | export const create_typescript_info = (
2 | version: string,
3 | path: string,
4 | title_case = true,
5 | ) => `${title_case ? 'U' : 'u'}sing TypeScript v${version} from ${path}`;
6 |
--------------------------------------------------------------------------------
/src/utils/find-docblock-options.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { find_docblock_options } from './find-docblock-options';
4 |
5 | it('should not read from normal comment', () => {
6 | expect(find_options('normal-comment')).toEqual({});
7 | });
8 |
9 | it('should not read from non-first docblock', () => {
10 | expect(find_options('second-docblock')).toEqual({});
11 | });
12 |
13 | it('should find enable:test-type correctly', () => {
14 | expect(find_options('enable-test-type')).toMatchSnapshot();
15 | });
16 |
17 | it('should find enable:test-value correctly', () => {
18 | expect(find_options('enable-test-value')).toMatchSnapshot();
19 | });
20 |
21 | it('should find disable:test-type correctly', () => {
22 | expect(find_options('disable-test-type')).toMatchSnapshot();
23 | });
24 |
25 | it('should find disable:test-value correctly', () => {
26 | expect(find_options('disable-test-value')).toMatchSnapshot();
27 | });
28 |
29 | it('should find multi option value correctly', () => {
30 | expect(find_options('multi-option-value')).toMatchSnapshot();
31 | });
32 |
33 | it('should throw error if there is unexpected value', () => {
34 | expect(() => find_options('unexpected')).toThrowErrorMatchingSnapshot();
35 | });
36 |
37 | function find_options(id: string) {
38 | const source_file = load_fixture_source_file(
39 | `find-docblock-options/${id}.ts`,
40 | ts,
41 | );
42 | return find_docblock_options(source_file, ts);
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/find-docblock-options.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import {
3 | docblock_option_regex,
4 | DocblockOptions,
5 | DocblockOptionMatchIndex,
6 | DocblockOptionValue,
7 | } from '../definitions';
8 | import { for_each_comment } from './for-each-comment';
9 |
10 | export const find_docblock_options = (
11 | source_file: _ts.SourceFile,
12 | ts: typeof _ts,
13 | ) => {
14 | const options: DocblockOptions = {};
15 |
16 | for_each_comment(
17 | source_file,
18 | comment => {
19 | if (!comment.startsWith('/**')) {
20 | return false;
21 | }
22 |
23 | const matched = comment.match(docblock_option_regex);
24 |
25 | if (matched === null) {
26 | return false;
27 | }
28 |
29 | const options_literal = matched[DocblockOptionMatchIndex.Options].trim();
30 |
31 | options_literal.split(/\s+/).forEach(option_value => {
32 | switch (option_value) {
33 | case DocblockOptionValue.EnableTestType:
34 | options.test_type = true;
35 | break;
36 | case DocblockOptionValue.EnableTestValue:
37 | options.test_value = true;
38 | break;
39 | case DocblockOptionValue.DisableTestType:
40 | options.test_type = false;
41 | break;
42 | case DocblockOptionValue.DisableTestValue:
43 | options.test_value = false;
44 | break;
45 | default:
46 | throw new Error(`Unexpected option value '${option_value}'`);
47 | }
48 | });
49 |
50 | return false;
51 | },
52 | ts,
53 | );
54 |
55 | return options;
56 | };
57 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-bodies.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { find_trigger_bodies } from './find-trigger-bodies';
4 | import { find_trigger_headers } from './find-trigger-headers';
5 |
6 | it('should return correctly', () => {
7 | expect(find_bodies('simple')).toMatchSnapshot();
8 | });
9 |
10 | it('should throw error if there are some unattachable bodies', () => {
11 | expect(() => find_bodies('unattachable')).toThrowErrorMatchingSnapshot();
12 | });
13 |
14 | it('should throw error if there are some invalid groups', () => {
15 | expect(() => find_bodies('invalid-group')).toThrowErrorMatchingSnapshot();
16 | });
17 |
18 | function find_bodies(id: string) {
19 | const source_file = load_fixture_source_file(
20 | `find-trigger-bodies/${id}.ts`,
21 | ts,
22 | );
23 | const headers = find_trigger_headers(source_file, ts);
24 | return find_trigger_bodies(source_file, headers, ts);
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-bodies.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import { TriggerBody, TriggerGroup, TriggerHeader } from '../definitions';
3 | import { create_message } from './create-message';
4 | import { get_display_line } from './get-display-line';
5 | import { get_node_one_line_text } from './get-node-one-line-text';
6 | import { get_trigger_groups } from './get-trigger-groups';
7 | import { get_trigger_header_line } from './get-trigger-line';
8 | import { traverse_node } from './traverse-node';
9 |
10 | export const find_trigger_bodies = (
11 | source_file: _ts.SourceFile,
12 | headers: TriggerHeader[],
13 | ts: typeof _ts,
14 | ) => {
15 | const bodies: TriggerBody[] = [];
16 |
17 | const header_map = new Map();
18 | headers.forEach(header => header_map.set(header.line, header));
19 |
20 | const groups = get_trigger_groups(headers);
21 | const invalid_groups: TriggerGroup[] = [];
22 |
23 | traverse_node(
24 | source_file,
25 | node => {
26 | const start = node.getStart();
27 | const end = node.getEnd();
28 |
29 | const body_line = source_file.getLineAndCharacterOfPosition(start).line;
30 | const body_end_line = source_file.getLineAndCharacterOfPosition(end).line;
31 |
32 | // check if group position is valid (top-level comment)
33 | if (groups.length !== 0) {
34 | const [first_group] = groups;
35 |
36 | if (first_group.line < body_line) {
37 | // checked
38 | groups.shift();
39 | } else if (
40 | // group is not at the top-level (surrounded by node)
41 | first_group.line >= body_line &&
42 | first_group.line <= body_end_line
43 | ) {
44 | invalid_groups.push(groups.shift()!);
45 | }
46 | }
47 |
48 | const header_line = get_trigger_header_line(body_line);
49 |
50 | if (
51 | node.kind <= ts.SyntaxKind.LastTriviaToken ||
52 | !header_map.has(header_line)
53 | ) {
54 | return;
55 | }
56 |
57 | header_map.delete(header_line);
58 |
59 | bodies.push({
60 | start,
61 | end,
62 | experssion: get_node_one_line_text(node, source_file, ts),
63 | text: get_dedented_expression_text(node, source_file)
64 | // remove trailing semicolons and spaces
65 | .replace(/\s*;*\s*$/, ''),
66 | });
67 | },
68 | ts,
69 | );
70 |
71 | // unattachable_lines
72 | if (header_map.size !== 0) {
73 | const unattachable_line_infos: string[] = [];
74 | header_map.forEach((header, line) => {
75 | const { description = '' } = header;
76 | unattachable_line_infos.push(
77 | `${source_file.fileName}:${get_display_line(line)} ${description}`,
78 | );
79 | });
80 | throw new Error(
81 | create_message(
82 | 'Unattachable trigger(s) detected:',
83 | unattachable_line_infos,
84 | ),
85 | );
86 | }
87 |
88 | if (invalid_groups.length !== 0) {
89 | throw new Error(
90 | create_message(
91 | 'Invalid trigger-group(s) detected:',
92 | invalid_groups.map(
93 | ({ line, description = '' }) =>
94 | `${source_file.fileName}:${get_display_line(line)} ${description}`,
95 | ),
96 | ),
97 | );
98 | }
99 |
100 | return bodies;
101 | };
102 |
103 | function get_dedented_expression_text(
104 | node: _ts.Node,
105 | source_file: _ts.SourceFile,
106 | ) {
107 | const start = node.getStart();
108 | const { character } = source_file.getLineAndCharacterOfPosition(start);
109 |
110 | return (
111 | node
112 | .getText(source_file)
113 | // dedent
114 | .replace(/^ */gm, spaces =>
115 | ' '.repeat(Math.max(0, spaces.length - character)),
116 | )
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-footers.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { find_trigger_bodies } from './find-trigger-bodies';
4 | import { find_trigger_footers } from './find-trigger-footers';
5 | import { find_trigger_headers } from './find-trigger-headers';
6 |
7 | it('should return correctly', () => {
8 | expect(find_footers('simple')).toMatchSnapshot();
9 | });
10 |
11 | it('should throw error if there are some unattachable footers', () => {
12 | expect(() => find_footers('unattachable')).toThrowErrorMatchingSnapshot();
13 | });
14 |
15 | function find_footers(id: string) {
16 | const source_file = load_fixture_source_file(
17 | `find-trigger-footers/${id}.ts`,
18 | ts,
19 | );
20 | const headers = find_trigger_headers(source_file, ts);
21 | const bodies = find_trigger_bodies(source_file, headers, ts);
22 | return find_trigger_footers(source_file, bodies, ts);
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-footers.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import {
3 | trigger_footer_regex,
4 | TriggerBody,
5 | TriggerFooter,
6 | TriggerFooterFlag,
7 | TriggerFooterMatchIndex,
8 | } from '../definitions';
9 | import { create_message } from './create-message';
10 | import { for_each_comment } from './for-each-comment';
11 | import { get_comment_content } from './get-comment-content';
12 | import { get_display_line } from './get-display-line';
13 | import { get_trigger_body_end_line } from './get-trigger-line';
14 | import { normalize_expected_value } from './normalize-expected-value';
15 |
16 | export const find_trigger_footers = (
17 | source_file: _ts.SourceFile,
18 | bodies: TriggerBody[],
19 | ts: typeof _ts,
20 | ) => {
21 | const footers: TriggerFooter[] = [];
22 |
23 | const body_end_line_set = new Set(
24 | bodies.map(
25 | body => source_file.getLineAndCharacterOfPosition(body.end).line,
26 | ),
27 | );
28 |
29 | const unattachable_lines: number[] = [];
30 |
31 | for_each_comment(
32 | source_file,
33 | (comment, scanner) => {
34 | const match = get_comment_content(comment).match(trigger_footer_regex);
35 |
36 | if (match === null) {
37 | return;
38 | }
39 |
40 | const start = scanner.getTokenPos();
41 | const { line: footer_line } =
42 | source_file.getLineAndCharacterOfPosition(start);
43 |
44 | const body_end_line = get_trigger_body_end_line(footer_line);
45 |
46 | if (!body_end_line_set.has(body_end_line)) {
47 | unattachable_lines.push(footer_line);
48 | return;
49 | }
50 |
51 | body_end_line_set.delete(body_end_line);
52 |
53 | const value = match[TriggerFooterMatchIndex.Value];
54 |
55 | switch (value) {
56 | case TriggerFooterFlag.Show:
57 | case TriggerFooterFlag.Error:
58 | case TriggerFooterFlag.NoError:
59 | footers.push({
60 | line: footer_line,
61 | flag: value,
62 | });
63 | break;
64 | default:
65 | footers.push({
66 | line: footer_line,
67 | expected: normalize_expected_value(
68 | value,
69 | source_file.fileName,
70 | footer_line,
71 | ts,
72 | ),
73 | });
74 | break;
75 | }
76 | },
77 | ts,
78 | );
79 |
80 | if (unattachable_lines.length !== 0) {
81 | throw new Error(
82 | create_message(
83 | 'Unattachable trigger-footer(s) detected:',
84 | unattachable_lines.map(
85 | line => `${source_file.fileName}:${get_display_line(line)}`,
86 | ),
87 | ),
88 | );
89 | }
90 |
91 | return footers;
92 | };
93 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-headers.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { trigger_header_serializer } from '../helpers/trigger-header-serializer';
4 | import { find_trigger_headers } from './find-trigger-headers';
5 |
6 | expect.addSnapshotSerializer(trigger_header_serializer);
7 |
8 | it('should return correctly', () => {
9 | expect(find_headers('simple')).toMatchSnapshot();
10 | });
11 |
12 | it('should return correctly with group', () => {
13 | expect(find_headers('group')).toMatchSnapshot();
14 | });
15 |
16 | it('should return correctly with method', () => {
17 | expect(find_headers('method')).toMatchSnapshot();
18 | });
19 |
20 | it('should do nothing with unmatched comment', () => {
21 | expect(find_headers('unmatched')).toHaveLength(0);
22 | });
23 |
24 | it('should treat @ts-expect-error as @dts-jest:fail', () => {
25 | expect(find_headers('ts-expect-error')).toEqual(
26 | find_headers('ts-expect-error-equivalent'),
27 | );
28 | });
29 |
30 | function find_headers(id: string) {
31 | return find_trigger_headers(
32 | load_fixture_source_file(`find-trigger-headers/${id}.ts`, ts),
33 | ts,
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/find-trigger-headers.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import {
3 | trigger_header_regex,
4 | GroupMethod,
5 | TestMethod,
6 | TriggerGroup,
7 | TriggerHeader,
8 | TriggerHeaderFlags,
9 | TriggerHeaderMatchIndex,
10 | } from '../definitions';
11 | import { for_each_comment } from './for-each-comment';
12 | import { get_comment_content } from './get-comment-content';
13 | import { parse_trigger_header_flags } from './parse-trigger-header-flags';
14 |
15 | export const find_trigger_headers = (
16 | source_file: _ts.SourceFile,
17 | ts: typeof _ts,
18 | ) => {
19 | const headers: TriggerHeader[] = [];
20 |
21 | let last_group: TriggerGroup | undefined;
22 |
23 | for_each_comment(
24 | source_file,
25 | (comment, scanner) => {
26 | const match = get_comment_content(comment)
27 | // treat `@ts-expect-error` as `@dts-jest:fail`
28 | .replace(/^\s*@ts-expect-error/, '@dts-jest:fail')
29 | .match(trigger_header_regex);
30 |
31 | if (match === null) {
32 | return;
33 | }
34 |
35 | const description = match[TriggerHeaderMatchIndex.Description];
36 | const flags = parse_trigger_header_flags(
37 | match[TriggerHeaderMatchIndex.Flags],
38 | );
39 |
40 | const start = scanner.getTokenPos();
41 | const { line } = source_file.getLineAndCharacterOfPosition(start);
42 |
43 | if (flags & TriggerHeaderFlags[':group']) {
44 | last_group = {
45 | line,
46 | method: get_group_method(flags),
47 | description: description.length === 0 ? undefined : description,
48 | };
49 | } else {
50 | headers.push({
51 | line,
52 | flags,
53 | method: get_test_method(flags),
54 | description: description.length === 0 ? undefined : description,
55 | group: last_group,
56 | });
57 | }
58 | },
59 | ts,
60 | );
61 |
62 | return headers;
63 | };
64 |
65 | function get_test_method(flag: TriggerHeaderFlags) {
66 | return flag & TriggerHeaderFlags[':only']
67 | ? TestMethod.Only
68 | : flag & TriggerHeaderFlags[':skip']
69 | ? TestMethod.Skip
70 | : TestMethod.Test;
71 | }
72 |
73 | function get_group_method(flag: TriggerHeaderFlags) {
74 | return flag & TriggerHeaderFlags[':only']
75 | ? GroupMethod.Only
76 | : flag & TriggerHeaderFlags[':skip']
77 | ? GroupMethod.Skip
78 | : GroupMethod.Test;
79 | }
80 |
--------------------------------------------------------------------------------
/src/utils/find-triggers.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { load_fixture_source_file } from '../helpers/load-fixture';
3 | import { trigger_header_serializer } from '../helpers/trigger-header-serializer';
4 | import { find_triggers } from './find-triggers';
5 |
6 | expect.addSnapshotSerializer(trigger_header_serializer);
7 |
8 | it('should return correctly', () => {
9 | const source_file = load_fixture_source_file(`find-triggers/example.ts`, ts);
10 | expect(find_triggers(source_file, ts)).toMatchSnapshot();
11 | });
12 |
--------------------------------------------------------------------------------
/src/utils/find-triggers.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import { Trigger, TriggerFooter } from '../definitions';
3 | import { find_trigger_bodies } from './find-trigger-bodies';
4 | import { find_trigger_footers } from './find-trigger-footers';
5 | import { find_trigger_headers } from './find-trigger-headers';
6 | import { get_trigger_body_end_line } from './get-trigger-line';
7 |
8 | export const find_triggers = (source_file: _ts.SourceFile, ts: typeof _ts) => {
9 | const triggers: Trigger[] = [];
10 |
11 | const headers = find_trigger_headers(source_file, ts);
12 | const bodies = find_trigger_bodies(source_file, headers, ts);
13 | const footers = find_trigger_footers(source_file, bodies, ts);
14 |
15 | for (let i = 0; i < headers.length; i++) {
16 | const header = headers[i];
17 | const body = bodies[i];
18 | let footer: TriggerFooter | undefined;
19 |
20 | const { line: body_end_line } = source_file.getLineAndCharacterOfPosition(
21 | body.end,
22 | );
23 |
24 | if (
25 | footers.length !== 0 &&
26 | body_end_line === get_trigger_body_end_line(footers[0].line)
27 | ) {
28 | footer = footers.shift();
29 | }
30 |
31 | triggers.push({
32 | header,
33 | body,
34 | footer,
35 | });
36 | }
37 |
38 | return triggers;
39 | };
40 |
--------------------------------------------------------------------------------
/src/utils/for-each-comment.ts:
--------------------------------------------------------------------------------
1 | import { for_each, ForEachCallback } from 'ts-comment';
2 | import * as _ts from 'typescript';
3 |
4 | export const for_each_comment: (
5 | source: string | _ts.SourceFile,
6 | callback: ForEachCallback,
7 | ts: typeof _ts,
8 | ) => void = for_each as any;
9 |
--------------------------------------------------------------------------------
/src/utils/get-comment-content.test.ts:
--------------------------------------------------------------------------------
1 | import { get_comment_content } from './get-comment-content';
2 |
3 | it('should return correctly for single line comment', () => {
4 | expect(get_comment_content('// single-line-comment')).toMatchSnapshot();
5 | });
6 |
7 | it('should return correctly for multi line comment', () => {
8 | expect(get_comment_content('/* multi-line-comment */')).toMatchSnapshot();
9 | });
10 |
--------------------------------------------------------------------------------
/src/utils/get-comment-content.ts:
--------------------------------------------------------------------------------
1 | export const get_comment_content = (comment: string) =>
2 | comment.startsWith('//')
3 | ? comment.slice(2).trim()
4 | : comment.slice(2, -2).trim();
5 |
--------------------------------------------------------------------------------
/src/utils/get-description-for-jest.ts:
--------------------------------------------------------------------------------
1 | import { Trigger } from '../definitions';
2 |
3 | const jest_title_leading_spaces_count = 2;
4 | const jest_grouped_title_leading_spaces_count = 4;
5 |
6 | export const get_description_for_jest = (trigger: Trigger) => {
7 | const { header, body } = trigger;
8 | const indentation = ' '.repeat(
9 | header.group === undefined
10 | ? jest_title_leading_spaces_count
11 | : jest_grouped_title_leading_spaces_count,
12 | );
13 | const description =
14 | header.description !== undefined ? header.description : body.text;
15 | return description.replace(/\n/g, `\n${indentation}`);
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/get-diagnostic-message.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | export const get_diagnostic_message = (diagnostic: _ts.Diagnostic) =>
4 | // istanbul ignore next
5 | typeof diagnostic.messageText === 'string'
6 | ? diagnostic.messageText
7 | : diagnostic.messageText.messageText;
8 |
--------------------------------------------------------------------------------
/src/utils/get-display-line.ts:
--------------------------------------------------------------------------------
1 | export const get_display_line = (line: number) => line + 1;
2 |
--------------------------------------------------------------------------------
/src/utils/get-group-description.ts:
--------------------------------------------------------------------------------
1 | import { TriggerGroup } from '../definitions';
2 |
3 | export const get_group_description = (group: TriggerGroup) => {
4 | // istanbul ignore next
5 | const { description = '' } = group;
6 | return description;
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/get-node-one-line-text.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | export const get_node_one_line_text = (
4 | node: _ts.Node,
5 | source_file: _ts.SourceFile,
6 | ts: typeof _ts,
7 | ) => {
8 | const printer = ts.createPrinter(
9 | { removeComments: true },
10 | {
11 | substituteNode: (_hint, current_node) =>
12 | ts.transform(current_node, [escape_newlines_in_template_node])
13 | .transformed[0],
14 | },
15 | );
16 |
17 | return printer
18 | .printNode(ts.EmitHint.Unspecified, node, source_file)
19 | .replace(/\s*\n\s*/g, ' ')
20 | .replace(/;+$/, '');
21 |
22 | function escape_newlines_in_template_node(
23 | context: _ts.TransformationContext,
24 | ) {
25 | return (node: _ts.Node) => {
26 | if (ts.isNoSubstitutionTemplateLiteral(node) && node.rawText) {
27 | return context.factory.createNoSubstitutionTemplateLiteral(
28 | node.text,
29 | escape_newlines(node.rawText),
30 | );
31 | }
32 | if (ts.isTemplateHead(node) && node.rawText) {
33 | return context.factory.createTemplateHead(
34 | node.text,
35 | escape_newlines(node.rawText),
36 | // @ts-expect-error: internal api
37 | node.templateFlags,
38 | );
39 | }
40 | if (ts.isTemplateMiddle(node) && node.rawText) {
41 | return context.factory.createTemplateMiddle(
42 | node.text,
43 | escape_newlines(node.rawText),
44 | // @ts-expect-error: internal api
45 | node.templateFlags,
46 | );
47 | }
48 | if (ts.isTemplateTail(node) && node.rawText) {
49 | return context.factory.createTemplateTail(
50 | node.text,
51 | escape_newlines(node.rawText),
52 | // @ts-expect-error: internal api
53 | node.templateFlags,
54 | );
55 | }
56 | return node;
57 | };
58 | }
59 | };
60 |
61 | function escape_newlines(raw_text: string) {
62 | // hack for TypeScript 3.6+ (ref: https://github.com/microsoft/TypeScript/pull/32844)
63 | return raw_text.replace(/\n/g, '\\n');
64 | }
65 |
--------------------------------------------------------------------------------
/src/utils/get-snapshot-description.ts:
--------------------------------------------------------------------------------
1 | import { Trigger } from '../definitions';
2 | import { snapshot_assertion_message } from './create-assertion-expression';
3 | import { get_description_for_jest } from './get-description-for-jest';
4 | import { get_group_description } from './get-group-description';
5 |
6 | export const get_snapshot_description = (trigger: Trigger) => {
7 | const test_description = get_description_for_jest(trigger);
8 | if (trigger.header.group === undefined) {
9 | return `${test_description} ${snapshot_assertion_message}`;
10 | }
11 | const group_description = get_group_description(trigger.header.group);
12 | return `${group_description} ${test_description} ${snapshot_assertion_message}`;
13 | };
14 |
--------------------------------------------------------------------------------
/src/utils/get-trigger-groups.ts:
--------------------------------------------------------------------------------
1 | import { TriggerGroup, TriggerHeader } from '../definitions';
2 |
3 | export const get_trigger_groups = (headers: TriggerHeader[]) => {
4 | const group_set = new Set();
5 | headers.forEach(header => {
6 | if (header.group !== undefined) {
7 | group_set.add(header.group);
8 | }
9 | });
10 | return Array.from(group_set);
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/get-trigger-line.ts:
--------------------------------------------------------------------------------
1 | export const get_trigger_header_line = (body_line: number) => body_line - 1;
2 | export const get_trigger_body_line = (header_line: number) => header_line + 1;
3 | export const get_trigger_body_end_line = (footer_line: number) => footer_line;
4 |
--------------------------------------------------------------------------------
/src/utils/indent.test.ts:
--------------------------------------------------------------------------------
1 | import { indent } from './indent';
2 |
3 | it('should return correctly', () => {
4 | expect(indent('line 1\nline 2\nline 3', 2)).toMatchSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/src/utils/indent.ts:
--------------------------------------------------------------------------------
1 | export const indent = (str: string, spaces: number) =>
2 | str.replace(/^/gm, ' '.repeat(spaces));
3 |
--------------------------------------------------------------------------------
/src/utils/load-compiler-options.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { cwd_serializer } from '../helpers/cwd-serializer';
3 | import { get_fixture_filename } from '../helpers/load-fixture';
4 | import { load_compiler_options } from './load-compiler-options';
5 |
6 | expect.addSnapshotSerializer(cwd_serializer);
7 |
8 | it('should return correctly with tsconfig (filename)', () => {
9 | expect(load_options('example')).toMatchSnapshot();
10 | });
11 |
12 | it('should return correctly with tsconfig (raw-options)', () => {
13 | expect(
14 | load_options({
15 | module: 'commonjs',
16 | target: 'es6',
17 | strict: true,
18 | }),
19 | ).toMatchSnapshot();
20 | });
21 |
22 | it('should throw error if there are some invalid values', () => {
23 | expect(() =>
24 | load_options({
25 | module: 'commonjssssss',
26 | target: 'es666666',
27 | }),
28 | ).toThrowErrorMatchingSnapshot();
29 | });
30 |
31 | it('should throw error if parse tsconfig failed', () => {
32 | expect(() => load_options('invalid')).toThrowErrorMatchingSnapshot();
33 | });
34 |
35 | function load_options(raw_options: string | Record) {
36 | return load_compiler_options(
37 | typeof raw_options === 'string'
38 | ? get_fixture_filename(
39 | `load-compiler-options/${raw_options}/tsconfig.json`,
40 | )
41 | : raw_options,
42 | ts,
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/utils/load-compiler-options.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as _ts from 'typescript';
3 | import { create_message } from './create-message';
4 | import { get_diagnostic_message } from './get-diagnostic-message';
5 |
6 | export const load_compiler_options = (
7 | raw_options: string | Record,
8 | ts: typeof _ts,
9 | ): { options: _ts.CompilerOptions; file_names: string[] } =>
10 | typeof raw_options === 'string'
11 | ? load_from_tsconfig(raw_options, ts)
12 | : load_from_raw_options(raw_options, ts);
13 |
14 | function load_from_raw_options(
15 | raw_options: Record,
16 | ts: typeof _ts,
17 | ) {
18 | const { errors, options } = ts.convertCompilerOptionsFromJson(
19 | raw_options,
20 | process.cwd(),
21 | );
22 |
23 | if (errors.length !== 0) {
24 | throw new Error(
25 | create_message(
26 | 'Unexpected error(s) while loading compiler options:',
27 | errors.map(get_diagnostic_message),
28 | ),
29 | );
30 | }
31 |
32 | return { options, file_names: [] };
33 | }
34 |
35 | function load_from_tsconfig(filename: string, ts: typeof _ts) {
36 | const { config: tsconfig, error } = ts.readConfigFile(
37 | filename,
38 | ts.sys.readFile,
39 | );
40 |
41 | // istanbul ignore next
42 | if (error !== undefined) {
43 | throw new Error(
44 | create_message(
45 | `Unexpected error(s) while reading tsconfig (${filename}):`,
46 | [get_diagnostic_message(error)],
47 | ),
48 | );
49 | }
50 |
51 | const dirname = path.dirname(filename);
52 |
53 | const {
54 | errors,
55 | options,
56 | fileNames: file_names,
57 | } = ts.parseJsonConfigFileContent(tsconfig, ts.sys, dirname);
58 |
59 | if (errors.length !== 0) {
60 | throw new Error(
61 | create_message(
62 | `Unexpected error(s) while parsing tsconfig (${filename}):`,
63 | errors.map(get_diagnostic_message),
64 | ),
65 | );
66 | }
67 |
68 | return { options, file_names };
69 | }
70 |
--------------------------------------------------------------------------------
/src/utils/load-typescript.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | export const load_typescript = (id: string = 'typescript') => {
4 | const typescript_path = require.resolve(id);
5 | const typescript = require(typescript_path) as typeof _ts;
6 | return { typescript, typescript_path };
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/normalize-config.ts:
--------------------------------------------------------------------------------
1 | import { NormalizedConfig, RawConfig } from '../definitions';
2 | import { load_compiler_options } from './load-compiler-options';
3 | import { load_typescript } from './load-typescript';
4 | import { replace_cwd } from './replace-cwd';
5 |
6 | export const normalize_config = (
7 | raw_config: RawConfig = {},
8 | ): NormalizedConfig => {
9 | // istanbul ignore next
10 | const {
11 | test_type = true,
12 | test_value = false,
13 | compiler_options: raw_compiler_options = {},
14 | enclosing_declaration = false,
15 | typescript: typescript_id = 'typescript',
16 | transpile = true,
17 | } = raw_config;
18 |
19 | const { typescript, typescript_path } = load_typescript(
20 | replace_cwd(typescript_id),
21 | );
22 |
23 | // istanbul ignore next
24 | const { type_format_flags = typescript.TypeFormatFlags.NoTruncation } =
25 | raw_config;
26 |
27 | const compiler_options = load_compiler_options(
28 | raw_compiler_options,
29 | typescript,
30 | );
31 |
32 | return {
33 | transpile,
34 | test_type,
35 | test_value,
36 | compiler_options: compiler_options.options,
37 | file_names: compiler_options.file_names,
38 | type_format_flags,
39 | enclosing_declaration,
40 | typescript,
41 | typescript_path,
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/src/utils/normalize-expected-value.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { normalize_expected_value } from './normalize-expected-value';
3 |
4 | it('should remove newline correctly', () => {
5 | expect(
6 | normalize(`
7 | (() => {
8 | switch (x) {
9 | case 0:
10 | return -1;
11 | default:
12 | return 1;
13 | }
14 | })()
15 | `),
16 | ).toMatchSnapshot();
17 | });
18 |
19 | it('should replace newline character with escaped newline correctly (NoSubstitutionTemplateLiteral)', () => {
20 | expect(normalize('`123\n456\n789`')).toMatchSnapshot();
21 | });
22 |
23 | it('should replace newline character with escaped newline correctly (TemplateHead/TemplateMiddle/TemplateTail)', () => {
24 | expect(normalize('`12${3}\n456\n${7}89`')).toMatchSnapshot();
25 | });
26 |
27 | it('should remove comments correctly', () => {
28 | expect(
29 | normalize(`
30 | // comment
31 | 'test'
32 | `),
33 | ).toMatchSnapshot();
34 | });
35 |
36 | it('should remove trailing semicolon correctly', () => {
37 | expect(normalize('123')).toMatchSnapshot();
38 | });
39 |
40 | it('should throw error if parsing error', () => {
41 | expect(() => normalize('fun tion () {}')).toThrowErrorMatchingSnapshot();
42 | });
43 |
44 | function normalize(value: string) {
45 | return normalize_expected_value(value, 'somewhere.ts', -1, ts);
46 | }
47 |
--------------------------------------------------------------------------------
/src/utils/normalize-expected-value.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 | import { create_message } from './create-message';
3 | import { create_source_file } from './create-source-file';
4 | import { get_diagnostic_message } from './get-diagnostic-message';
5 | import { get_display_line } from './get-display-line';
6 | import { get_node_one_line_text } from './get-node-one-line-text';
7 |
8 | interface InternalSourceFile extends _ts.SourceFile {
9 | parseDiagnostics?: _ts.Diagnostic[];
10 | }
11 |
12 | export const normalize_expected_value = (
13 | value: string,
14 | filename: string,
15 | line: number,
16 | ts: typeof _ts,
17 | ) => {
18 | const source_file = create_source_file(
19 | filename,
20 | `var x = ${value}`,
21 | ts,
22 | ) as InternalSourceFile;
23 |
24 | // istanbul ignore next
25 | const { parseDiagnostics: errors = [] } = source_file;
26 |
27 | if (errors.length !== 0) {
28 | const position_information = `(${filename}:${get_display_line(line)})`;
29 | throw new Error(
30 | create_message(`Unexpected error(s) while parsing expected value:`, [
31 | position_information,
32 | '',
33 | ...errors.map(get_diagnostic_message),
34 | ]),
35 | );
36 | }
37 |
38 | const expression = (source_file.statements[0] as _ts.VariableStatement)
39 | .declarationList.declarations[0].initializer!;
40 |
41 | return get_node_one_line_text(expression, source_file, ts);
42 | };
43 |
--------------------------------------------------------------------------------
/src/utils/normalize-trigger-header-methods.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { TestMethod, TriggerHeader } from '../definitions';
3 | import { load_fixture_source_file } from '../helpers/load-fixture';
4 | import { find_trigger_headers } from './find-trigger-headers';
5 | import { normalize_trigger_header_methods } from './normalize-trigger-header-methods';
6 |
7 | it('should not be affected without only', () => {
8 | const headers = get_normalized_headers('no-only');
9 | expect(get_headers_info(headers)).toMatchSnapshot();
10 | expect(get_header_only_count(headers)).toBe(0);
11 | });
12 |
13 | it('should normalize correctly with group-only', () => {
14 | const headers = get_normalized_headers('group-only');
15 | expect(get_headers_info(headers)).toMatchSnapshot();
16 | expect(get_header_only_count(headers)).toBe(0);
17 | });
18 |
19 | it('should normalize correctly with trigger-only', () => {
20 | const headers = get_normalized_headers('trigger-only');
21 | expect(get_headers_info(headers)).toMatchSnapshot();
22 | expect(get_header_only_count(headers)).toBe(0);
23 | });
24 |
25 | it('should normalize correctly with trigger-only in group-only', () => {
26 | const headers = get_normalized_headers('trigger-only-in-group-only');
27 | expect(get_headers_info(headers)).toMatchSnapshot();
28 | expect(get_header_only_count(headers)).toBe(0);
29 | });
30 |
31 | it('should normalize correctly with trigger-only in group-skip', () => {
32 | const headers = get_normalized_headers('trigger-only-in-group-skip');
33 | expect(get_headers_info(headers)).toMatchSnapshot();
34 | expect(get_header_only_count(headers)).toBe(0);
35 | });
36 |
37 | it('should normalize correctly with multi group-only', () => {
38 | const headers = get_normalized_headers('multi-group-only');
39 | expect(get_headers_info(headers)).toMatchSnapshot();
40 | expect(get_header_only_count(headers)).toBe(0);
41 | });
42 |
43 | it('should normalize correctly with multi trigger-only', () => {
44 | const headers = get_normalized_headers('multi-trigger-only');
45 | expect(get_headers_info(headers)).toMatchSnapshot();
46 | expect(get_header_only_count(headers)).toBe(0);
47 | });
48 |
49 | it('should normalize correctly with multi group-only and trigger-only', () => {
50 | const headers = get_normalized_headers('multi-mixed-only');
51 | expect(get_headers_info(headers)).toMatchSnapshot();
52 | expect(get_header_only_count(headers)).toBe(0);
53 | });
54 |
55 | function get_normalized_headers(id: string) {
56 | const source_file = load_fixture_source_file(
57 | `normalize-trigger-header-methods/${id}.ts`,
58 | ts,
59 | );
60 | const headers = find_trigger_headers(source_file, ts);
61 | normalize_trigger_header_methods(headers);
62 | return headers;
63 | }
64 |
65 | function get_header_only_count(headers: TriggerHeader[]) {
66 | return headers.filter(header => header.method === TestMethod.Only).length;
67 | }
68 |
69 | function get_headers_info(headers: TriggerHeader[]) {
70 | return headers.map(header => `${header.description} -> ${header.method}`);
71 | }
72 |
--------------------------------------------------------------------------------
/src/utils/normalize-trigger-header-methods.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GroupMethod,
3 | TestMethod,
4 | TriggerGroup,
5 | TriggerHeader,
6 | } from '../definitions';
7 |
8 | export const normalize_trigger_header_methods = (headers: TriggerHeader[]) => {
9 | const has_only_method = headers.some(
10 | header =>
11 | header.method === TestMethod.Only ||
12 | (header.group !== undefined && header.group.method === GroupMethod.Only),
13 | );
14 |
15 | if (!has_only_method) {
16 | return;
17 | }
18 |
19 | headers.forEach(header => {
20 | if (
21 | header.method === TestMethod.Test &&
22 | (header.group === undefined || header.group.method === GroupMethod.Test)
23 | ) {
24 | header.method = TestMethod.Skip;
25 | }
26 | });
27 |
28 | const group_map = new Map();
29 | headers.forEach(header => {
30 | set_array_map(group_map, header.group, header);
31 | });
32 | group_map.forEach((grouped_triggers, group) => {
33 | if (group !== undefined) {
34 | normalize_grouped_trigger_methods(grouped_triggers);
35 | }
36 | });
37 |
38 | headers.forEach(header => {
39 | if (header.method === TestMethod.Only) {
40 | header.method = TestMethod.Test;
41 | }
42 | });
43 | };
44 |
45 | function set_array_map(map: Map, key: T, value: U) {
46 | const values = map.get(key);
47 | if (values === undefined) {
48 | map.set(key, [value]);
49 | } else {
50 | values.push(value);
51 | }
52 | }
53 |
54 | function normalize_grouped_trigger_methods(headers: TriggerHeader[]) {
55 | const test_headers: TriggerHeader[] = [];
56 | const only_headers: TriggerHeader[] = [];
57 |
58 | const is_skip_group = headers[0].group!.method === GroupMethod.Skip;
59 |
60 | headers.forEach(header => {
61 | if (header.method === TestMethod.Test) {
62 | test_headers.push(header);
63 | } else if (header.method === TestMethod.Only) {
64 | only_headers.push(header);
65 | }
66 | });
67 |
68 | if (is_skip_group || only_headers.length !== 0) {
69 | test_headers.forEach(header => (header.method = TestMethod.Skip));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/utils/parse-trigger-header-flags.test.ts:
--------------------------------------------------------------------------------
1 | import { parse_trigger_header_flags } from './parse-trigger-header-flags';
2 |
3 | it('should parse correclty', () => {
4 | expect(parse_trigger_header_flags(':pass:snap')).toMatchSnapshot();
5 | });
6 |
7 | it('should throw error if there is a duplicate flag', () => {
8 | expect(() =>
9 | parse_trigger_header_flags(':fail:fail'),
10 | ).toThrowErrorMatchingSnapshot();
11 | });
12 |
13 | it('should throw error if there is an unexpected flag', () => {
14 | expect(() =>
15 | parse_trigger_header_flags(':unexpected'),
16 | ).toThrowErrorMatchingSnapshot();
17 | });
18 |
19 | it('should throw error if group flag and assertion flag appear simultaneously ', () => {
20 | expect(() =>
21 | parse_trigger_header_flags(':group:pass'),
22 | ).toThrowErrorMatchingSnapshot();
23 | });
24 |
25 | it('should throw error if pass flag and fail flag appear simultaneously ', () => {
26 | expect(() =>
27 | parse_trigger_header_flags(':pass:fail'),
28 | ).toThrowErrorMatchingSnapshot();
29 | });
30 |
--------------------------------------------------------------------------------
/src/utils/parse-trigger-header-flags.ts:
--------------------------------------------------------------------------------
1 | import { TriggerHeaderFlags } from '../definitions';
2 |
3 | export const parse_trigger_header_flags = (unparsed_flags: string) => {
4 | let flags = TriggerHeaderFlags.None;
5 |
6 | unparsed_flags
7 | .split(/(?=:)/)
8 | .filter(flag_literal => flag_literal.length !== 0)
9 | .forEach(flag_literal => {
10 | switch (flag_literal) {
11 | case TriggerHeaderFlags[TriggerHeaderFlags[':fail']]:
12 | case TriggerHeaderFlags[TriggerHeaderFlags[':pass']]:
13 | case TriggerHeaderFlags[TriggerHeaderFlags[':show']]:
14 | case TriggerHeaderFlags[TriggerHeaderFlags[':snap']]:
15 | case TriggerHeaderFlags[TriggerHeaderFlags[':not-any']]:
16 | case TriggerHeaderFlags[TriggerHeaderFlags[':only']]:
17 | case TriggerHeaderFlags[TriggerHeaderFlags[':skip']]:
18 | case TriggerHeaderFlags[TriggerHeaderFlags[':group']]:
19 | const current_flag: TriggerHeaderFlags = TriggerHeaderFlags[
20 | flag_literal as any
21 | ] as any;
22 | if (flags & current_flag) {
23 | throw new Error(`Duplicate flag '${flag_literal}'`);
24 | }
25 | flags = flags | current_flag;
26 | break;
27 | default:
28 | throw new Error(`Unexpected flag '${flag_literal}'`);
29 | }
30 | });
31 |
32 | if (
33 | flags & TriggerHeaderFlags[':pass'] &&
34 | flags & TriggerHeaderFlags[':fail']
35 | ) {
36 | const pass_flag_literal = TriggerHeaderFlags[TriggerHeaderFlags[':pass']];
37 | const fail_flag_literal = TriggerHeaderFlags[TriggerHeaderFlags[':fail']];
38 | throw new Error(
39 | `${pass_flag_literal} and ${fail_flag_literal} cannot be used simultaneously'`,
40 | );
41 | }
42 |
43 | if (
44 | flags & TriggerHeaderFlags[':group'] &&
45 | flags & TriggerHeaderFlags.Assertion
46 | ) {
47 | const group_flag_literal = TriggerHeaderFlags[TriggerHeaderFlags[':group']];
48 | throw new Error(
49 | `AssertionFlag is not allowed to be used with flag '${group_flag_literal}'`,
50 | );
51 | }
52 |
53 | return flags;
54 | };
55 |
--------------------------------------------------------------------------------
/src/utils/replace-cwd.ts:
--------------------------------------------------------------------------------
1 | export const replace_cwd = (str: string) => str.replace('', process.cwd());
2 |
--------------------------------------------------------------------------------
/src/utils/traverse-node.test.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { create_source_file } from './create-source-file';
3 | import { traverse_node } from './traverse-node';
4 |
5 | const source = `
6 |
7 | Math.max(1, 2, 3);
8 |
9 | function abc() {
10 | console.log('something');
11 | }
12 |
13 | `;
14 |
15 | it('should work correctly', () => {
16 | const source_file = create_source_file('something.ts', source, ts);
17 |
18 | const records: any[] = [];
19 |
20 | traverse_node(
21 | source_file,
22 | node => {
23 | records.push({
24 | kind: ts.SyntaxKind[node.kind],
25 | text: node.getText(),
26 | });
27 | },
28 | ts,
29 | );
30 |
31 | expect(records).toMatchSnapshot();
32 | });
33 |
--------------------------------------------------------------------------------
/src/utils/traverse-node.ts:
--------------------------------------------------------------------------------
1 | import * as _ts from 'typescript';
2 |
3 | export const traverse_node = (
4 | node: _ts.Node,
5 | callback: (node: _ts.Node) => void,
6 | ts: typeof _ts,
7 | ) => {
8 | if (node.kind !== ts.SyntaxKind.SourceFile) {
9 | callback(node);
10 | }
11 | ts.forEachChild(node, child => traverse_node(child, callback, ts));
12 | };
13 |
--------------------------------------------------------------------------------
/tests/__snapshots__/example.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`A Math.max('123') (type) should match snapshot 1`] = `"Argument of type 'string' is not assignable to parameter of type 'number'."`;
4 |
5 | exports[`A Math.max('456') (type) should match snapshot 1`] = `"Argument of type 'string' is not assignable to parameter of type 'number'."`;
6 |
7 | exports[`A Math.max(2) (type) should match snapshot 1`] = `"number"`;
8 |
--------------------------------------------------------------------------------
/tests/example.snap.ts:
--------------------------------------------------------------------------------
1 | /** @dts-jest enable:test-value */
2 |
3 | // @dts-jest:pass
4 | Math.max(1); //=> 1
5 |
6 | // @dts-jest:group A
7 |
8 | // @dts-jest:pass:snap -> number
9 | Math.max(2); //=> 2
10 |
11 | // @dts-jest:fail:snap -> Argument of type 'string' is not assignable to parameter of type 'number'.
12 | Math.max('123');
13 |
14 | // @ts-expect-error:snap -> Argument of type 'string' is not assignable to parameter of type 'number'.
15 | Math.max('456');
16 |
17 | // @dts-jest:group B
18 |
19 | // @dts-jest:show
20 | Math.max(4); //=> ?
21 |
22 | // @dts-jest:pass
23 | Object.assign({
24 | a: 1,
25 | }, {
26 | b: 2,
27 | }, {
28 | c: 3,
29 | }); /*=>
30 | {
31 | a: 1,
32 | b: 2,
33 | c: 3,
34 | }
35 | */
36 |
37 | // @dts-jest:not-any
38 | Math.max(5);
39 |
--------------------------------------------------------------------------------
/tests/example.ts:
--------------------------------------------------------------------------------
1 | /** @dts-jest enable:test-value */
2 |
3 | // @dts-jest:pass
4 | Math.max(1); //=> 1
5 |
6 | // @dts-jest:group A
7 |
8 | // @dts-jest:pass:snap
9 | Math.max(2); //=> 2
10 |
11 | // @dts-jest:fail:snap
12 | Math.max('123');
13 |
14 | // @ts-expect-error:snap
15 | Math.max('456');
16 |
17 | // @dts-jest:group B
18 |
19 | // @dts-jest:show
20 | Math.max(4); //=> ?
21 |
22 | // @dts-jest:pass
23 | Object.assign({
24 | a: 1,
25 | }, {
26 | b: 2,
27 | }, {
28 | c: 3,
29 | }); /*=>
30 | {
31 | a: 1,
32 | b: 2,
33 | c: 3,
34 | }
35 | */
36 |
37 | // @dts-jest:not-any
38 | Math.max(5);
39 |
--------------------------------------------------------------------------------
/tests/jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "testEnvironment": "node",
3 | "moduleFileExtensions": ["ts", "js", "json"],
4 | "rootDir": "..",
5 | "testMatch": ["/tests/example.ts"],
6 | "transform": {
7 | "\\.ts$": "/transform"
8 | },
9 | "reporters": ["default", "/reporter"],
10 | "globals": {
11 | "_dts_jest_": {
12 | "compiler_options": {
13 | "module": "commonjs",
14 | "strict": true,
15 | "target": "es6"
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/transform.js:
--------------------------------------------------------------------------------
1 | exports.process = require('./lib/index').transform;
2 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["src/index.ts"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "lib"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "module": "commonjs",
5 | "strict": true,
6 | "lib": ["es6"],
7 | "importHelpers": true
8 | },
9 | "include": ["src/**/*.ts"]
10 | }
11 |
--------------------------------------------------------------------------------