├── .gitattributes ├── .gitignore ├── .screenrc ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── expect_test.bs.js.snap ├── expect_test.res ├── globals_only_test.res ├── globals_test.res ├── jest_test.res ├── mockjs_test.res ├── reason_syntax_test.res ├── runner_only_test.res └── runner_test.res ├── babel.config.js ├── bsconfig.json ├── jest.config.js ├── package-lock.json ├── package.json └── src ├── jest.res └── jest.resi /.gitattributes: -------------------------------------------------------------------------------- 1 | # Tell github that .res and .resi files are ReScript 2 | *.res linguist-language=ReScript 3 | *.resi linguist-language=ReScript 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Package Manager 2 | 3 | .yarn/* 4 | !.yarn/patches 5 | !.yarn/plugins 6 | !.yarn/releases 7 | !.yarn/sdks 8 | !.yarn/versions 9 | 10 | node_modules/ 11 | .merlin 12 | .bsb.lock 13 | **/*.bs.js 14 | **/*.mjs 15 | lib/* 16 | !lib/js 17 | lib/js/* 18 | !lib/js/src 19 | coverage 20 | .coveralls.yml 21 | -------------------------------------------------------------------------------- /.screenrc: -------------------------------------------------------------------------------- 1 | sessionname "rescript-jest test console" 2 | screen -t "bsb -- `C-a \` to quit" 0 npm run watch:rescript 3 | split 4 | focus down 5 | screen -t "jest -- `C-a \` to quit - `C-a Esc` to scroll" 1 npm run watch:jest 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - 10 5 | cache: yarn 6 | script: npm run test-ci 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${file}" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 glennsl 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 | # Rescript-jest 2 | 3 | [ReScript](https://github.com/rescript-lang) bindings for [Jest](https://github.com/facebook/jest) 4 | 5 | [![npm](https://img.shields.io/npm/v/@glennsl/rescript-jest.svg)](https://npmjs.org/@glennsl/rescript-jest) 6 | [![Travis](https://img.shields.io/travis/glennsl/rescript-jest/master.svg)](https://travis-ci.org/glennsl/rescript-jest) 7 | [![Coverage](https://img.shields.io/coveralls/glennsl/rescript-jest/master.svg)](https://coveralls.io/github/glennsl/rescript-jest?branch=master) 8 | [![Issues](https://img.shields.io/github/issues/glennsl/rescript-jest.svg)](https://github.com/glennsl/rescript-jest/issues) 9 | [![Last Commit](https://img.shields.io/github/last-commit/glennsl/rescript-jest.svg)](https://github.com/glennsl/rescript-jest/commits/master) 10 | 11 | **NOTE:** The _NPM package has moved to `@glennsl/rescript-jest`. Remember to update both `package.json` AND `bsconfig.json`._ 12 | 13 | ## Status 14 | 15 | ### Rescript-jest 16 | - bs-jest is rebranded as rescript-jest 17 | - rescript-jest depends on Rescript 9.1.4, Jest 27.3.1 and @ryyppy/rescript-promise 2.1.0. 18 | - Starting from Jest 27.0.0 jest-jasmine was replaced by jest-circus changing the semantics for before and after hooks. `afterAllAsync` and `afterAllPromise` hooks now time-out consistent with the behavior of `beforeAllAsync` and `beforeAllPromise` in version 0.7.0 of bs-jest. `beforeAllAsync` and `beforeAllPromise` also now behave consistently with '`afterAllAsync` and `afterAllPromise` when included in skipped test suites. 19 | - rescript-jest API now uses data-first semantics throughout and uses `rescript-promise` in place of `Js.Promise`. 20 | - usefakeTimers() binding updated to address changes in the Jest fake timer API (useFakeTimer(~implementation=[#legacy|#modern], ())) 21 | - Deprecated BuckleScript `@bs.send.pipe` bindings were converted to rescript `@send` bindings. 22 | - All tests have been updated to reflect semantic and behavioral changes. 23 | - Babel modules have been added as dev dependencies to make generated bs-jest bindings available in ES6 module format. 24 | - Babel and Jest config files are included illustrating how to transform ES6 modules for Jest. 25 | 26 | To generate ES6 bindings for your project, update bsconfig.json 27 | 28 | ```js 29 | "suffix": ".mjs", 30 | "package-specs": { 31 | "module": "ES6", 32 | "in-source": true 33 | }, 34 | ``` 35 | Then add `@babel/core`, `@babel/preset-env` and `babel-jest` packages to your project. Also, add babel.config.js 36 | 37 | ```js 38 | module.exports = { 39 | presets: [ 40 | ['@babel/preset-env', 41 | {targets: {node: 'current'}} 42 | ] 43 | ], 44 | "plugins": [] 45 | } 46 | ``` 47 | 48 | Finally, add minimal jest.config.js 49 | 50 | ```js 51 | module.exports = { 52 | moduleFileExtensions: [ 53 | "js", 54 | "mjs", 55 | ], 56 | testMatch: [ 57 | "**/__tests__/**/*_test.mjs", 58 | "**/__tests__/**/*_test.bs.js", 59 | ], 60 | transform: { 61 | "^.+\.m?js$": "babel-jest" 62 | }, 63 | transformIgnorePatterns: [ 64 | "node_modules/(?!(rescript)/)" 65 | ], 66 | ``` 67 | 68 | Update testMatch, transform and transformIgnorePatterns settings depending on where your tests are stored, and other dependenies of your project that may need to be transformed to ES6 format. 69 | 70 | Most of what's commonly used is very stable. But the more js-y parts should be considered experimental, such as mocking and some of the expects that don't transfer well, or just don't make sense for testing idiomatic Reason/OCaml code but could be useful for testing js interop. 71 | 72 | - [Global](https://facebook.github.io/jest/docs/en/api.html): Fully implemented and tested, apart from `require.*` 73 | - [Expect](https://facebook.github.io/jest/docs/en/expect.html): Mostly implemented. Functionality that makes sense only for JS interop have been moved to `ExpectJs`. Some functionality does not make sense in a typed language, or is not possible to implement sensibly in Rescript. 74 | - [Mock Functions](https://facebook.github.io/jest/docs/en/mock-function-api.html): Experimental and unsafe implementation, very much in flux. The Jest bindings will most likely be relegated to the `MockJs` module as it's very quirky to use with native code. A separate native from-scratch implementation might suddenly appear as `Mock`. 75 | - [The Jest Object](https://facebook.github.io/jest/docs/en/jest-object.html): Fake timers are fully implemented and tested. Mock functionality has been moved to `JestJs`. It's mostly implemented, but experimental and largely untested. 76 | - **Snapshotting**: Expect functions exist and work, but there's currently no way to implement custom snapshot serializers. 77 | 78 | ## Example 79 | 80 | ```rescript 81 | open Jest; 82 | 83 | describe("Expect", () => { 84 | open Expect; 85 | 86 | test("toBe", () => 87 | expect(1 + 2) -> toBe(3)) 88 | }); 89 | 90 | describe("Expect.Operators", () => { 91 | open Expect; 92 | open! Expect.Operators; 93 | 94 | test("==", () => 95 | expect(1 + 2) === 3) 96 | } 97 | ); 98 | 99 | ``` 100 | 101 | See [the tests](https://github.com/glennsl/rescript-jest/tree/master/__tests__) for more examples. 102 | 103 | ## Installation 104 | 105 | ```sh 106 | npm install --save-dev @glennsl/rescript-jest 107 | ``` 108 | 109 | or 110 | 111 | ``` 112 | yarn install --save-dev @glennsl/rescript-jest 113 | ``` 114 | 115 | Then add `@glennsl/rescript-jest` to `bs-dev-dependencies` in your `bsconfig.json`: 116 | 117 | ```js 118 | { 119 | ... 120 | "bs-dev-dependencies": ["@glennsl/rescript-jest"] 121 | } 122 | ``` 123 | 124 | Then add `__tests__` to `sources` in your `bsconfig.json`: 125 | 126 | ```js 127 | "sources": [ 128 | { 129 | "dir": "src" 130 | }, 131 | { 132 | "dir": "__tests__", 133 | "type": "dev" 134 | } 135 | ] 136 | ``` 137 | 138 | ## Usage 139 | 140 | Put tests in a `__tests__` directory and use the suffix `*test.res`/ (Make sure to use valid module names. e.g. `_test.res` is valid while `.test.res` is not). When compiled they will be put in a `__tests__` directory under `lib`, with a `*test.bs.js` suffix, ready to be picked up when you run `jest`. If you're not already familiar with [Jest](https://github.com/facebook/jest), see [the Jest documentation](https://facebook.github.io/jest/). 141 | 142 | One very important difference from Jest is that assertions are not imperative. That is, `expect(1 + 2) -> toBe(3)`, for example, will not "execute" the assertion then and there. It will instead return an `assertion` value which must be returned from the test function. Only after the test function has completed will the returned assertion be checked. Any other assertions will be ignored, but unless you explicitly ignore them, it will produce compiler warnings about unused values. **This means there can be at most one assertion per test**. But it also means there must be at least one assertion per test. You can't forget an assertion in a branch, and think the test passes when in fact it doesn't even test anything. It will also force you to write simple tests that are easy to understand and refactor, and will give you more information about what's wrong when something does go wrong. 143 | 144 | At first sight this may still seem very limiting, and if you write very imperative code it really is, but I'd argue the real problem then is the imperative code. There are however some workarounds that can alleviate this: 145 | 146 | - Compare multiple values by wrapping them in a tuple: `expect((this, that)) -> toBe((3, 4))` 147 | - Use the `testAll` function to generate tests based on a list of data 148 | - Use `describe` and/or `beforeAll` to do setup for a group of tests. Code written in Rescript is immutable by default. Take advantage of it. 149 | - Write a helper function if you find yourself repeating code. That's what functions are for, after all. You can even write a helper function to generate tests. 150 | - If you're still struggling, make an issue on GitHub or bring it up in Discord. We'll either figure out a good way to do it with what we already have, or realize that something actually is missing and add it. 151 | 152 | ## Documentation 153 | 154 | For the moment, please refer to [Jest.resi](https://github.com/glennsl/rescript-jest/blob/master/src/jest.resi). 155 | 156 | ## Extensions 157 | 158 | - [bs-jest-dom](https://redex.github.io/package/bs-jest-dom/) - Custom matchers to test the state of the DOM 159 | 160 | ## Troubleshooting 161 | 162 | If you encounter the error `SyntaxError: Cannot use import statement outside a module`, it may be that you are mixing `es6` and `commonjs` modules in your project. For example, this can happen when you are building a React project since React builds are always in ES6. To fix this, please do the following: 163 | 164 | - Make sure your `bsconfig.json` compiles `"es6"` or `"es6-global"`: 165 | ```json 166 | "package-specs": { 167 | "module": "es6", 168 | } 169 | ``` 170 | - Install [esbuild-jest](https://github.com/aelbore/esbuild-jest) through `yarn` or `npm` as a `devDependency`. 171 | - Build your Rescript project with deps: `rescript build -with-deps`. 172 | - Add this to your Jest config (or `jest` of your `package.json`): 173 | ```json 174 | { 175 | "transform": { 176 | "^.+\\.jsx?$": "esbuild-jest" 177 | }, 178 | "transformIgnorePatterns": ["/node_modules/(?!(rescript|@glennsl/rescript-jest)/)"] 179 | } 180 | ``` 181 | - The property `"transformIgnorePatterns"` is an array of strings. Either you do some regex or organize them in an array. **Please make sure all folders in `node_modules` involving compiled .res/.ml/.re files and the like such as `rescript` or `@glennsl/rescript-jest` are mentioned in the aforementioned array.** 182 | 183 | This problem is also addressed in [Issue #63](https://github.com/glennsl/rescript-jest/issues/63). 184 | 185 | ## Contribute 186 | 187 | ```sh 188 | git clone https://github.com/glennsl/rescript-jest.git 189 | cd rescript-jest 190 | npm install 191 | ``` 192 | 193 | Then build and run tests with `npm test`, start watchers for `rescript`and `jest` with `npm run watch:rescript` and `npm run watch:jest` respectively. Install `screen` to be able to use `npm run watch:screen` to run both watchers in a single terminal window. 194 | 195 | ## Changes 196 | 197 | ### 0.11 198 | - [BREAKING] Bump required rescript to 11.1.x, uncurried mode requires MockJs.fn to be applied with explcicit currying. 199 | - Worked around bug in rescript 11 curried mode where `@uncurry` causes an extra param which changes Jest behaviour, by replacing `@uncurry` with `(. )`. 200 | 201 | ### 0.10 202 | - [BREAKING] Bump required rescript to 10.1.x 203 | - Remove unnecessary dependency on `@ryyppy/rescript-promise` 204 | 205 | ### 0.9.2 206 | - Added `testAllPromise`. 207 | 208 | ### 0.9.1 209 | - Added `Jest.setSystemTime`. 210 | 211 | ### 0.9 212 | - [BREAKING] Removed the unnecessarily verbose generated namespace. 213 | 214 | ### 0.8 215 | - Moved repository from `glennsl/bs-jest` to `glennsl/rescript-jest` 216 | - Renamed published package to `@glennsl/rescript-jest` 217 | - [BREAKING] Converted source code to ReScript, hence will no longer work with versions of BuckleScript that lack ReScript support. 218 | - [BREAKING] As of Jest 27.0.0, Jest-Circus replaces Jest-Jasmine by default leading to change in behavior of async and Promise before and after hooks. 219 | - [BREAKING] As the `|>` operator is deprecated in Recript 9.x, all APIs now use data-first (`->`) semantics. 220 | 221 | ### 0.7 222 | 223 | - [BREAKING] Actually removed `toThrowException`, `toThrowMessage` and `toThrowMessageRe` as they relied on assumptions about BuckleScript internals that no longer hold. 224 | 225 | ### 0.6 226 | 227 | - Added `Expect.toContainEqual` 228 | - Updated to Jest 26.5.2 229 | - Upgraded bs-platform to 8.3.1 230 | 231 | ### 0.5.1 232 | 233 | - Added `Expect.toMatchInlineSnapshot` 234 | 235 | ### 0.5.0 236 | 237 | - Updated to Jest 25.1.0 238 | 239 | ### 0.4.9 240 | 241 | - Added `Todo.test` 242 | 243 | ### 0.4.8 244 | 245 | - Updated jest to 24.3.1 246 | - Fixed jest warnings not to return anything from `describe` callbacks by explicitly returning `undefined` (otherwise BuckleScript will return something else like `()`, which is represented as `0`) 247 | - Fixed several newly uncovered uncurrying issues caused by surprise breaking changes in BuckleScript (Thanks again, Bob!) 248 | - Added `Jest.advanceTimersByTime`, which is basically just an alias of `Jest.runTimersToTime` 249 | 250 | ### 0.4.7 251 | 252 | - Added `Expect.not__` for transitional compatibility with Reason syntax change of "unkeywording" `not` by mangling it into `not_`, and `not_` into `not__` and so on. 253 | 254 | ### 0.4.6 255 | 256 | - Made uncurrying explicit for `afterAllPromise` too. 257 | 258 | ### 0.4.5 259 | 260 | - Made uncurrying explicit to fix a breaking change in implicit uncurrying from `bs-platform` 4.0.7 (Thanks Bob!) 261 | 262 | ### 0.4.3 263 | 264 | - Removed some optimizations on skipped tests that Jest 23 suddenly started objecting to (#30) 265 | 266 | ### 0.4.0 267 | 268 | - Added `MockJs.new0`, `new1` and `new2` 269 | - Added `timeout` argument to `testAsync` and `testPromise` functions 270 | - Added `beforeEachAsync`, `beforeEachPromise`, `afterEachAsync` and `afterEachPromise` 271 | - Added `beforeAllAsync`, `beforeAllPromise`, `afterAllAsync` and `afterAllPromise` 272 | 273 | ### 0.3.1 274 | 275 | - Moved repository from `reasonml-community/bs-jest` to `glennsl/bs-jest` 276 | - Renamed NPM package from `bs-jest` to `@glennsl/bs-jest` 277 | 278 | ### 0.3.0 279 | 280 | - Added `toThrowException` 281 | - Fixed an issue with custom Runner implementation shadowing the global `test` function from jest 282 | - Fixed a typo in the js boundary of `not_ |> toBeLessThanEqual` 283 | 284 | ### 0.2.0 285 | 286 | - Removed deprecations 287 | - Added `testAll`, `Only.testAll`, `Skip.testAll` that generates tests from a list of inputs 288 | - Fixed type signature of `fail` 289 | - Added `expectFn` 290 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/expect_test.bs.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Expect toMatchSnapshot 1`] = `"foo"`; 4 | 5 | exports[`Expect toMatchSnapshotWithName: bar 1`] = `"foo"`; 6 | 7 | exports[`Expect toThrowErrorMatchingSnapshot 1`] = `"foo error"`; 8 | -------------------------------------------------------------------------------- /__tests__/expect_test.res: -------------------------------------------------------------------------------- 1 | open Jest 2 | 3 | type test_record = {value: string} 4 | 5 | let () = { 6 | describe("Expect", () => { 7 | open Expect 8 | 9 | test("toBe", () => expect(1 + 2)->toBe(3)) 10 | test("toBeCloseTo", () => expect(1. +. 2.)->toBeCloseTo(3.)) 11 | test("toBeSoCloseTo", () => expect(1. +. 2.123)->toBeSoCloseTo(3.123, ~digits=3)) 12 | test("toBeGreaterThan", () => expect(4)->toBeGreaterThan(3)) 13 | test("toBeGreaterThanOrEqual", () => expect(4)->toBeGreaterThanOrEqual(4)) 14 | test("toBeLessThan", () => expect(4)->toBeLessThan(5)) 15 | test("toBeLessThanOrEqual", () => expect(4)->toBeLessThanOrEqual(4)) 16 | test("toBeSuperSetOf", () => expect(["a", "b", "c"])->toBeSupersetOf(["a", "c"])) 17 | test("toContain", () => expect(["a", "b", "c"])->toContain("b")) 18 | test("toContainEqual", () => 19 | expect([{value: "a"}, {value: "b"}, {value: "c"}])->toContainEqual({value: "b"}) 20 | ) 21 | test("toContainString", () => expect("banana")->toContainString("nana")) 22 | test("toHaveLength", () => expect(["a", "b", "c"])->toHaveLength(3)) 23 | test("toEqual", () => expect(1 + 2)->toEqual(3)) 24 | test("toMatch", () => expect("banana")->toMatch("nana")) 25 | test("toMatchRe", () => expect("banana")->toMatchRe(%re("/ana/"))) 26 | test("toThrow", () => expect(() => assert false)->toThrow) 27 | 28 | test("toMatchInlineSnapshot", () => expect("foo")->toMatchInlineSnapshot("\"foo\"")) 29 | test("toMatchSnapshot", () => expect("foo")->toMatchSnapshot) 30 | test("toMatchSnapshotWithName", () => expect("foo")->toMatchSnapshotWithName("bar")) 31 | test("toThrowErrorMatchingSnapshot", () => 32 | expect(() => Js.Exn.raiseError("foo error"))->toThrowErrorMatchingSnapshot 33 | ) 34 | 35 | test("not toBe", () => expect(1 + 2)->not_->toBe(4)) 36 | test("not toBeCloseTo", () => expect(1. +. 2.)->not_->toBeCloseTo(3001.)) 37 | test("not toBeSoCloseTo", () => expect(1. +. 2.123)->not_->toBeSoCloseTo(3.124, ~digits=3)) 38 | test("not toBeGreaterThan", () => expect(4)->not_->toBeGreaterThan(4)) 39 | test("not toBeGreaterThanOrEqual", () => expect(4)->not_->toBeGreaterThanOrEqual(5)) 40 | test("not toBeLessThan", () => expect(4)->not_->toBeLessThan(4)) 41 | test("not toBeLessThanOrEqual", () => expect(4)->not_->toBeLessThanOrEqual(3)) 42 | test("not toBeSuperSetOf", () => expect(["a", "b", "c"])->not_->toBeSupersetOf(["a", "d"])) 43 | test("not toContain", () => expect(["a", "b", "c"])->not_->toContain("d")) 44 | test("not toContainEqual", () => 45 | expect([{value: "a"}, {value: "b"}, {value: "c"}])->not_->toContainEqual({value: "d"}) 46 | ) 47 | test("not toContainString", () => expect("banana")->not_->toContainString("nanan")) 48 | test("not toHaveLength", () => expect(["a", "b", "c"])->not_->toHaveLength(2)) 49 | test("not toEqual", () => expect(1 + 2)->not_->toEqual(4)) 50 | test("not toMatch", () => expect("banana")->not_->toMatch("nanan")) 51 | test("not toMatchRe", () => expect("banana")->not_->toMatchRe(%re("/anas/"))) 52 | test("not toThrow", () => expect(() => 2)->not_->toThrow) 53 | 54 | test("expectFn", () => expectFn(raise, Invalid_argument("foo"))->toThrow) 55 | }) 56 | 57 | describe("Expect.Operators", () => { 58 | open Expect 59 | open! Expect.Operators 60 | 61 | test("==", () => expect(1 + 2) === 3) 62 | test(">", () => expect(4) > 3) 63 | test(">=", () => expect(4) >= 4) 64 | test("<", () => expect(4) < 5) 65 | test("<=", () => expect(4) <= 4) 66 | test("=", () => expect(1 + 2) == 3) 67 | test("<>", () => expect(1 + 2) != 4) 68 | test("!=", () => expect(1 + 2) !== 4) 69 | }) 70 | 71 | describe("ExpectJs", () => { 72 | open ExpectJs 73 | 74 | test("toBeDefined", () => expect(Js.Undefined.return(3))->toBeDefined) 75 | test("toBeFalsy", () => expect(nan)->toBeFalsy) 76 | test("toBeNull", () => expect(Js.null)->toBeNull) 77 | test("toBeTruthy", () => expect([])->toBeTruthy) 78 | test("toBeUndefined", () => expect(Js.Undefined.empty)->toBeUndefined) 79 | test("toContainProperties", () => 80 | expect({"foo": 0, "bar": true})->toContainProperties(["foo", "bar"]) 81 | ) 82 | test("toMatchObject", () => expect({"a": 1, "b": 2, "c": 3})->toMatchObject({"a": 1, "b": 2})) 83 | 84 | test("not toBeDefined", () => expect(Js.undefined)->not_->toBeDefined) 85 | test("not toBeFalsy", () => expect([])->not_->toBeFalsy) 86 | test("not toBeNull", () => expect(Js.Null.return(4))->not_->toBeNull) 87 | test("not toBeTruthy", () => expect(nan)->not_->toBeTruthy) 88 | test("not toBeUndefined", () => expect(Js.Undefined.return(4))->not_->toBeUndefined) 89 | test("not toContainProperties", () => 90 | expect({"foo": 0, "bar": true})->not_->toContainProperties(["foo", "zoo"]) 91 | ) 92 | test("not toMatchObject", () => 93 | expect({"a": 1, "b": 2, "c": 3})->not_->toMatchObject({"a": 1, "c": 2}) 94 | ) 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /__tests__/globals_only_test.res: -------------------------------------------------------------------------------- 1 | module Promise = Js.Promise2 2 | 3 | open Jest 4 | 5 | let () = { 6 | Only.test("Only.test", () => pass) 7 | 8 | Only.testAsync("Only.testAsync", finish => finish(pass)) 9 | Only.testAsync("testAsync - timeout ok", ~timeout=1, finish => finish(pass)) 10 | 11 | Only.testPromise("Only.testPromise", () => Promise.resolve(pass)) 12 | Only.testPromise("testPromise - timeout ok", ~timeout=1, () => Promise.resolve(pass)) 13 | 14 | Only.testAll("testAll", list{"foo", "bar", "baz"}, input => 15 | if Js.String.length(input) === 3 { 16 | pass 17 | } else { 18 | fail("") 19 | } 20 | ) 21 | Only.testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, (( 22 | input, 23 | output, 24 | )) => 25 | if Js.String.length(input) === output { 26 | pass 27 | } else { 28 | fail("") 29 | } 30 | ) 31 | Only.testAllPromise("testAllPromise", list{"foo", "bar", "baz"}, input => 32 | Promise.resolve( 33 | if Js.String.length(input) === 3 { 34 | pass 35 | } else { 36 | fail("") 37 | }, 38 | ) 39 | ) 40 | Only.testAllPromise( 41 | "testAllPromise - tuples", 42 | list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, 43 | ((input, output)) => 44 | Promise.resolve( 45 | if Js.String.length(input) === output { 46 | pass 47 | } else { 48 | fail("") 49 | }, 50 | ), 51 | ) 52 | 53 | Only.describe("Only.describe", () => test("some aspect", () => pass)) 54 | } 55 | -------------------------------------------------------------------------------- /__tests__/globals_test.res: -------------------------------------------------------------------------------- 1 | module Promise = Js.Promise2 2 | 3 | open Jest 4 | 5 | let () = { 6 | test("pass", () => pass) 7 | 8 | Skip.test("fail", () => fail("")) 9 | 10 | test("test", () => pass) 11 | 12 | Skip.test("test - expect fail", () => fail("")) 13 | 14 | testAsync("testAsync", finish => finish(pass)) 15 | 16 | Skip.testAsync("testAsync - no done", _ => ()) 17 | 18 | Skip.testAsync("testAsync - expect fail", finish => finish(fail(""))) 19 | 20 | testAsync("testAsync - timeout ok", ~timeout=1, finish => finish(pass)) 21 | 22 | Skip.testAsync("testAsync - timeout fail", ~timeout=1, _ => ()) 23 | 24 | testPromise("testPromise", () => Promise.resolve(pass)) 25 | 26 | Skip.testPromise("testPromise - reject", () => Promise.reject(Failure(""))) 27 | 28 | Skip.testPromise("testPromise - expect fail", () => Promise.resolve(fail(""))) 29 | 30 | testPromise("testPromise - timeout ok", ~timeout=1, () => Promise.resolve(pass)) 31 | 32 | Skip.testPromise("testPromise - timeout fail", ~timeout=1, () => 33 | Promise.make((~resolve as _, ~reject as _) => ()) 34 | ) 35 | 36 | testAll("testAll", list{"foo", "bar", "baz"}, input => 37 | if Js.String.length(input) === 3 { 38 | pass 39 | } else { 40 | fail("") 41 | } 42 | ) 43 | testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, ((input, output)) => 44 | if Js.String.length(input) === output { 45 | pass 46 | } else { 47 | fail("") 48 | } 49 | ) 50 | 51 | testAllPromise("testAllPromise", list{"foo", "bar", "baz"}, input => 52 | Promise.resolve( 53 | if Js.String.length(input) === 3 { 54 | pass 55 | } else { 56 | fail("") 57 | }, 58 | ) 59 | ) 60 | testAllPromise("testAllPromise - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, (( 61 | input, 62 | output, 63 | )) => 64 | Promise.resolve( 65 | if Js.String.length(input) === output { 66 | pass 67 | } else { 68 | fail("") 69 | }, 70 | ) 71 | ) 72 | 73 | describe("describe", () => test("some aspect", () => pass)) 74 | 75 | describe("beforeAll", () => { 76 | let x = ref(0) 77 | 78 | beforeAll(() => x := x.contents + 4) 79 | 80 | test("x is 4", () => 81 | if x.contents === 4 { 82 | pass 83 | } else { 84 | fail("") 85 | } 86 | ) 87 | test("x is still 4", () => 88 | if x.contents === 4 { 89 | pass 90 | } else { 91 | fail("") 92 | } 93 | ) 94 | }) 95 | 96 | describe("beforeAllAsync", () => { 97 | describe("without timeout", () => { 98 | let x = ref(0) 99 | 100 | beforeAllAsync( 101 | finish => { 102 | x := x.contents + 4 103 | finish() 104 | }, 105 | ) 106 | 107 | test( 108 | "x is 4", 109 | () => 110 | if x.contents === 4 { 111 | pass 112 | } else { 113 | fail("") 114 | }, 115 | ) 116 | test( 117 | "x is still 4", 118 | () => 119 | if x.contents === 4 { 120 | pass 121 | } else { 122 | fail("") 123 | }, 124 | ) 125 | }) 126 | 127 | describe("with 100ms timeout", () => { 128 | let x = ref(0) 129 | 130 | beforeAllAsync( 131 | ~timeout=100, 132 | finish => { 133 | x := x.contents + 4 134 | finish() 135 | }, 136 | ) 137 | 138 | test( 139 | "x is 4", 140 | () => 141 | if x.contents === 4 { 142 | pass 143 | } else { 144 | fail("") 145 | }, 146 | ) 147 | test( 148 | "x is still 4", 149 | () => 150 | if x.contents === 4 { 151 | pass 152 | } else { 153 | fail("") 154 | }, 155 | ) 156 | }) 157 | 158 | Skip.describe("timeout should fail suite", () => { 159 | beforeAllAsync(~timeout=1, _ => ()) 160 | test("", () => pass) /* runner will crash if there's no tests */ 161 | }) 162 | }) 163 | 164 | describe("beforeAllPromise", () => { 165 | describe("without timeout", () => { 166 | let x = ref(0) 167 | 168 | beforeAllPromise( 169 | () => { 170 | x := x.contents + 4 171 | Promise.resolve() 172 | }, 173 | ) 174 | 175 | test( 176 | "x is 4", 177 | () => 178 | if x.contents === 4 { 179 | pass 180 | } else { 181 | fail("") 182 | }, 183 | ) 184 | test( 185 | "x is still 4", 186 | () => 187 | if x.contents === 4 { 188 | pass 189 | } else { 190 | fail("") 191 | }, 192 | ) 193 | }) 194 | 195 | describe("with 100ms timeout", () => { 196 | let x = ref(0) 197 | 198 | beforeAllPromise( 199 | ~timeout=100, 200 | () => { 201 | x := x.contents + 4 202 | Promise.resolve() 203 | }, 204 | ) 205 | 206 | test( 207 | "x is 4", 208 | () => 209 | if x.contents === 4 { 210 | pass 211 | } else { 212 | fail("") 213 | }, 214 | ) 215 | test( 216 | "x is still 4", 217 | () => 218 | if x.contents === 4 { 219 | pass 220 | } else { 221 | fail("") 222 | }, 223 | ) 224 | }) 225 | 226 | Skip.describe("timeout should fail suite", () => { 227 | beforeAllPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 228 | test("", () => pass) /* runner will crash if there's no tests */ 229 | }) 230 | }) 231 | 232 | describe("beforeEach", () => { 233 | let x = ref(0) 234 | 235 | beforeEach(() => x := x.contents + 4) 236 | 237 | test("x is 4", () => 238 | if x.contents === 4 { 239 | pass 240 | } else { 241 | fail("") 242 | } 243 | ) 244 | test("x is suddenly 8", () => 245 | if x.contents === 8 { 246 | pass 247 | } else { 248 | fail("") 249 | } 250 | ) 251 | }) 252 | 253 | describe("beforeEachAsync", () => { 254 | describe("without timeout", () => { 255 | let x = ref(0) 256 | 257 | beforeEachAsync( 258 | finish => { 259 | x := x.contents + 4 260 | finish() 261 | }, 262 | ) 263 | 264 | test( 265 | "x is 4", 266 | () => 267 | if x.contents === 4 { 268 | pass 269 | } else { 270 | fail("") 271 | }, 272 | ) 273 | test( 274 | "x is suddenly 8", 275 | () => 276 | if x.contents === 8 { 277 | pass 278 | } else { 279 | fail("") 280 | }, 281 | ) 282 | }) 283 | 284 | describe("with 100ms timeout", () => { 285 | let x = ref(0) 286 | 287 | beforeEachAsync( 288 | ~timeout=100, 289 | finish => { 290 | x := x.contents + 4 291 | finish() 292 | }, 293 | ) 294 | 295 | test( 296 | "x is 4", 297 | () => 298 | if x.contents === 4 { 299 | pass 300 | } else { 301 | fail("") 302 | }, 303 | ) 304 | test( 305 | "x is suddenly 8", 306 | () => 307 | if x.contents === 8 { 308 | pass 309 | } else { 310 | fail("") 311 | }, 312 | ) 313 | }) 314 | 315 | Skip.describe("timeout should fail suite", () => { 316 | beforeEachAsync(~timeout=1, _ => ()) 317 | test("", () => pass) /* runner will crash if there's no tests */ 318 | }) 319 | }) 320 | 321 | describe("beforeEachPromise", () => { 322 | describe("without timeout", () => { 323 | let x = ref(0) 324 | 325 | beforeEachPromise( 326 | () => { 327 | x := x.contents + 4 328 | Promise.resolve(true) 329 | }, 330 | ) 331 | 332 | test( 333 | "x is 4", 334 | () => 335 | if x.contents === 4 { 336 | pass 337 | } else { 338 | fail("") 339 | }, 340 | ) 341 | test( 342 | "x is suddenly 8", 343 | () => 344 | if x.contents === 8 { 345 | pass 346 | } else { 347 | fail("") 348 | }, 349 | ) 350 | }) 351 | 352 | describe("with 100ms timeout", () => { 353 | let x = ref(0) 354 | 355 | beforeEachPromise( 356 | ~timeout=100, 357 | () => { 358 | x := x.contents + 4 359 | Promise.resolve(true) 360 | }, 361 | ) 362 | 363 | test( 364 | "x is 4", 365 | () => 366 | if x.contents === 4 { 367 | pass 368 | } else { 369 | fail("") 370 | }, 371 | ) 372 | test( 373 | "x is suddenly 8", 374 | () => 375 | if x.contents === 8 { 376 | pass 377 | } else { 378 | fail("") 379 | }, 380 | ) 381 | }) 382 | 383 | Skip.describe("timeout should fail suite", () => { 384 | beforeEachPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 385 | test("", () => pass) /* runner will crash if there's no tests */ 386 | }) 387 | }) 388 | 389 | describe("afterAll", () => { 390 | let x = ref(0) 391 | 392 | describe("phase 1", () => { 393 | afterAll(() => x := x.contents + 4) 394 | 395 | test( 396 | "x is 0", 397 | () => 398 | if x.contents === 0 { 399 | pass 400 | } else { 401 | fail("") 402 | }, 403 | ) 404 | test( 405 | "x is still 0", 406 | () => 407 | if x.contents === 0 { 408 | pass 409 | } else { 410 | fail("") 411 | }, 412 | ) 413 | }) 414 | 415 | describe("phase 2", () => 416 | test( 417 | "x is suddenly 4", 418 | () => 419 | if x.contents === 4 { 420 | pass 421 | } else { 422 | fail("") 423 | }, 424 | ) 425 | ) 426 | }) 427 | 428 | describe("afterAllAsync", () => { 429 | describe("without timeout", () => { 430 | let x = ref(0) 431 | 432 | describe( 433 | "phase 1", 434 | () => { 435 | afterAllAsync( 436 | finish => { 437 | x := x.contents + 4 438 | finish() 439 | }, 440 | ) 441 | 442 | test( 443 | "x is 0", 444 | () => 445 | if x.contents === 0 { 446 | pass 447 | } else { 448 | fail("") 449 | }, 450 | ) 451 | test( 452 | "x is still 0", 453 | () => 454 | if x.contents === 0 { 455 | pass 456 | } else { 457 | fail("") 458 | }, 459 | ) 460 | }, 461 | ) 462 | 463 | describe( 464 | "phase 2", 465 | () => 466 | test( 467 | "x is suddenly 4", 468 | () => 469 | if x.contents === 4 { 470 | pass 471 | } else { 472 | fail("") 473 | }, 474 | ), 475 | ) 476 | }) 477 | 478 | describe("with 100ms timeout", () => { 479 | let x = ref(0) 480 | 481 | describe( 482 | "phase 1", 483 | () => { 484 | afterAllAsync( 485 | ~timeout=100, 486 | finish => { 487 | x := x.contents + 4 488 | finish() 489 | }, 490 | ) 491 | 492 | test( 493 | "x is 0", 494 | () => 495 | if x.contents === 0 { 496 | pass 497 | } else { 498 | fail("") 499 | }, 500 | ) 501 | test( 502 | "x is still 0", 503 | () => 504 | if x.contents === 0 { 505 | pass 506 | } else { 507 | fail("") 508 | }, 509 | ) 510 | }, 511 | ) 512 | 513 | describe( 514 | "phase 2", 515 | () => 516 | test( 517 | "x is suddenly 4", 518 | () => 519 | if x.contents === 4 { 520 | pass 521 | } else { 522 | fail("") 523 | }, 524 | ), 525 | ) 526 | }) 527 | 528 | Skip.describe("timeout should fail suite", () => { 529 | afterAllAsync(~timeout=1, _ => ()) 530 | test("", () => pass) /* runner will crash if there's no tests */ 531 | }) 532 | }) 533 | 534 | describe("afterAllPromise", () => { 535 | describe("without timeout", () => { 536 | let x = ref(0) 537 | 538 | describe( 539 | "phase 1", 540 | () => { 541 | afterAllPromise( 542 | () => { 543 | x := x.contents + 4 544 | Promise.resolve(true) 545 | }, 546 | ) 547 | 548 | test( 549 | "x is 0", 550 | () => 551 | if x.contents === 0 { 552 | pass 553 | } else { 554 | fail("") 555 | }, 556 | ) 557 | test( 558 | "x is still 0", 559 | () => 560 | if x.contents === 0 { 561 | pass 562 | } else { 563 | fail("") 564 | }, 565 | ) 566 | }, 567 | ) 568 | 569 | describe( 570 | "phase 2", 571 | () => 572 | test( 573 | "x is suddenly 4", 574 | () => 575 | if x.contents === 4 { 576 | pass 577 | } else { 578 | fail("") 579 | }, 580 | ), 581 | ) 582 | }) 583 | 584 | describe("with 100ms timeout", () => { 585 | let x = ref(0) 586 | 587 | describe( 588 | "phase 1", 589 | () => { 590 | afterAllPromise( 591 | ~timeout=100, 592 | () => { 593 | x := x.contents + 4 594 | Promise.resolve(true) 595 | }, 596 | ) 597 | 598 | test( 599 | "x is 0", 600 | () => 601 | if x.contents === 0 { 602 | pass 603 | } else { 604 | fail("") 605 | }, 606 | ) 607 | test( 608 | "x is still 0", 609 | () => 610 | if x.contents === 0 { 611 | pass 612 | } else { 613 | fail("") 614 | }, 615 | ) 616 | }, 617 | ) 618 | 619 | describe( 620 | "phase 2", 621 | () => 622 | test( 623 | "x is suddenly 4", 624 | () => 625 | if x.contents === 4 { 626 | pass 627 | } else { 628 | fail("") 629 | }, 630 | ), 631 | ) 632 | }) 633 | 634 | Skip.describe("timeout should fail suite", () => { 635 | afterAllPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 636 | test("", () => pass) /* runner will crash if there's no tests */ 637 | }) 638 | }) 639 | 640 | describe("afterEach", () => { 641 | let x = ref(0) 642 | 643 | afterEach(() => x := x.contents + 4) 644 | 645 | test("x is 0", () => 646 | if x.contents === 0 { 647 | pass 648 | } else { 649 | fail("") 650 | } 651 | ) 652 | test("x is suddenly 4", () => 653 | if x.contents === 4 { 654 | pass 655 | } else { 656 | fail("") 657 | } 658 | ) 659 | }) 660 | 661 | describe("afterEachAsync", () => { 662 | describe("without timeout", () => { 663 | let x = ref(0) 664 | 665 | afterEachAsync( 666 | finish => { 667 | x := x.contents + 4 668 | finish() 669 | }, 670 | ) 671 | 672 | test( 673 | "x is 0", 674 | () => 675 | if x.contents === 0 { 676 | pass 677 | } else { 678 | fail("") 679 | }, 680 | ) 681 | test( 682 | "x is suddenly 4", 683 | () => 684 | if x.contents === 4 { 685 | pass 686 | } else { 687 | fail("") 688 | }, 689 | ) 690 | }) 691 | 692 | describe("with 100ms timeout", () => { 693 | let x = ref(0) 694 | 695 | afterEachAsync( 696 | ~timeout=100, 697 | finish => { 698 | x := x.contents + 4 699 | finish() 700 | }, 701 | ) 702 | 703 | test( 704 | "x is 0", 705 | () => 706 | if x.contents === 0 { 707 | pass 708 | } else { 709 | fail("") 710 | }, 711 | ) 712 | test( 713 | "x is suddenly 4", 714 | () => 715 | if x.contents === 4 { 716 | pass 717 | } else { 718 | fail("") 719 | }, 720 | ) 721 | }) 722 | 723 | Skip.describe("timeout should fail suite", () => { 724 | afterEachAsync(~timeout=1, _ => ()) 725 | test("", () => pass) /* runner will crash if there's no tests */ 726 | }) 727 | }) 728 | 729 | describe("afterEachPromise", () => { 730 | describe("without timeout", () => { 731 | let x = ref(0) 732 | 733 | afterEachPromise( 734 | () => { 735 | x := x.contents + 4 736 | Promise.resolve(true) 737 | }, 738 | ) 739 | 740 | test( 741 | "x is 0", 742 | () => 743 | if x.contents === 0 { 744 | pass 745 | } else { 746 | fail("") 747 | }, 748 | ) 749 | test( 750 | "x is suddenly 4", 751 | () => 752 | if x.contents === 4 { 753 | pass 754 | } else { 755 | fail("") 756 | }, 757 | ) 758 | }) 759 | 760 | describe("with 100ms timeout", () => { 761 | let x = ref(0) 762 | 763 | afterEachPromise( 764 | ~timeout=100, 765 | () => { 766 | x := x.contents + 4 767 | Promise.resolve(true) 768 | }, 769 | ) 770 | 771 | test( 772 | "x is 0", 773 | () => 774 | if x.contents === 0 { 775 | pass 776 | } else { 777 | fail("") 778 | }, 779 | ) 780 | test( 781 | "x is suddenly 4", 782 | () => 783 | if x.contents === 4 { 784 | pass 785 | } else { 786 | fail("") 787 | }, 788 | ) 789 | }) 790 | 791 | Skip.describe("timeout should fail suite", () => { 792 | afterEachPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 793 | test("", () => pass) /* runner will crash if there's no tests */ 794 | }) 795 | }) 796 | 797 | describe("Only", () => 798 | /* See globals_only_test.ml */ 799 | () 800 | ) 801 | 802 | describe("Skip", () => { 803 | Skip.test("Skip.test", () => pass) 804 | 805 | Skip.testAsync("Skip.testAsync", finish => finish(pass)) 806 | Skip.testAsync("Skip.testAsync - timeout", ~timeout=1, _ => ()) 807 | 808 | Skip.testPromise("Skip.testPromise", () => Promise.resolve(pass)) 809 | Skip.testPromise("testPromise - timeout", ~timeout=1, () => 810 | Promise.make((~resolve as _, ~reject as _) => ()) 811 | ) 812 | 813 | Skip.testAll("testAll", list{"foo", "bar", "baz"}, input => 814 | if Js.String.length(input) === 3 { 815 | pass 816 | } else { 817 | fail("") 818 | } 819 | ) 820 | Skip.testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, (( 821 | input, 822 | output, 823 | )) => 824 | if Js.String.length(input) === output { 825 | pass 826 | } else { 827 | fail("") 828 | } 829 | ) 830 | Skip.testAllPromise("testAllPromise", list{"foo", "bar", "baz"}, input => 831 | Promise.resolve( 832 | if Js.String.length(input) === 3 { 833 | pass 834 | } else { 835 | fail("") 836 | }, 837 | ) 838 | ) 839 | Skip.testAllPromise( 840 | "testAllPromise - tuples", 841 | list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, 842 | ((input, output)) => 843 | Promise.resolve( 844 | if Js.String.length(input) === output { 845 | pass 846 | } else { 847 | fail("") 848 | }, 849 | ), 850 | ) 851 | 852 | Skip.describe("Skip.describe", () => test("some aspect", () => pass)) 853 | }) 854 | 855 | describe("Todo", () => Todo.test("Todo.test")) 856 | } 857 | -------------------------------------------------------------------------------- /__tests__/jest_test.res: -------------------------------------------------------------------------------- 1 | open Jest 2 | open Expect 3 | open! Expect.Operators 4 | 5 | @val external setTimeout: (unit => unit, int) => unit = "setTimeout" 6 | @val external setImmediate: (unit => unit) => unit = "setImmediate" 7 | @val external nextTick: (unit => unit) => unit = "process.nextTick" 8 | 9 | let () = describe("Fake Timers", () => { 10 | test("runAllTimers", () => { 11 | let flag = ref(false) 12 | Jest.useFakeTimers() 13 | setTimeout(() => flag := true, 0) 14 | let before = flag.contents 15 | Jest.runAllTimers() 16 | 17 | expect((before, flag.contents)) == (false, true) 18 | }) 19 | 20 | test("runAllTicks", () => { 21 | let flag = ref(false) 22 | Jest.useFakeTimers() 23 | nextTick(() => flag := true) 24 | let before = flag.contents 25 | Jest.runAllTicks() 26 | 27 | expect((before, flag.contents)) == (false, true) 28 | }) 29 | 30 | test("runAllImmediates ", () => { 31 | let flag = ref(false) 32 | Jest.useFakeTimers(~implementation=#legacy, ()) 33 | setImmediate(() => flag := true) 34 | let before = flag.contents 35 | Jest.runAllImmediates() 36 | 37 | expect((before, flag.contents)) == (false, true) 38 | }) 39 | 40 | test("runTimersToTime", () => { 41 | let flag = ref(false) 42 | Jest.useFakeTimers(~implementation=#legacy, ()) 43 | setTimeout(() => flag := true, 1500) 44 | let before = flag.contents 45 | Jest.advanceTimersByTime(1000) 46 | let inbetween = flag.contents 47 | Jest.advanceTimersByTime(1000) 48 | 49 | expect((before, inbetween, flag.contents)) == (false, false, true) 50 | }) 51 | 52 | test("advanceTimersByTime", () => { 53 | let flag = ref(false) 54 | Jest.useFakeTimers(~implementation=#legacy, ()) 55 | setTimeout(() => flag := true, 1500) 56 | let before = flag.contents 57 | Jest.advanceTimersByTime(1000) 58 | let inbetween = flag.contents 59 | Jest.advanceTimersByTime(1000) 60 | 61 | expect((before, inbetween, flag.contents)) == (false, false, true) 62 | }) 63 | 64 | test("runOnlyPendingTimers", () => { 65 | let count = ref(0) 66 | Jest.useFakeTimers(~implementation=#legacy, ()) 67 | let rec recursiveTimeout = () => { 68 | count := count.contents + 1 69 | setTimeout(recursiveTimeout, 1500) 70 | } 71 | recursiveTimeout() 72 | let before = count.contents 73 | Jest.runOnlyPendingTimers() 74 | let inBetween = count.contents 75 | Jest.runOnlyPendingTimers() 76 | 77 | expect((before, inBetween, count.contents)) == (1, 2, 3) 78 | }) 79 | 80 | test("clearAllTimers", () => { 81 | let flag = ref(false) 82 | Jest.useFakeTimers() 83 | setImmediate(() => flag := true) 84 | let before = flag.contents 85 | Jest.clearAllTimers() 86 | Jest.runAllTimers() 87 | 88 | expect((before, flag.contents)) == (false, false) 89 | }) 90 | 91 | testAsync("clearAllTimers", finish => { 92 | Jest.useFakeTimers(~implementation=#legacy, ()) 93 | Jest.useRealTimers() 94 | setImmediate(() => finish(pass)) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /__tests__/mockjs_test.res: -------------------------------------------------------------------------------- 1 | open Jest 2 | open ExpectJs 3 | 4 | /* TODO: move to BS std lib */ 5 | @send external bind: ('a => 'b, 'c, 'a, 'a) => 'b = "bind" 6 | @send external bindThis: ('a => 'b, 'c) => 'a => 'b = "bind" 7 | @send external call: ('a => 'b, 'c, 'a) => 'b = "call" 8 | let call = (self, arg) => call(self, (), arg) 9 | @send external callThis: ('a => 'b, 'c, 'a) => 'b = "call" 10 | @send external call2: (('a, 'b) => 'c, @as(0) _, 'a, 'b) => 'c = "call" 11 | 12 | let _ = { 13 | describe("inferred_fn", _ => { 14 | test("returns undefined", _ => { 15 | let mockFn = JestJs.inferred_fn() 16 | let fn = MockJs.fn(mockFn) 17 | 18 | expect(call(fn, ()))->toBeUndefined 19 | }) 20 | 21 | test("black hole for argument type object", _ => { 22 | let mockFn = JestJs.inferred_fn() 23 | let fn = MockJs.fn(mockFn) 24 | 25 | expect(call(fn, {"property": 42}))->toBeUndefined 26 | }) 27 | 28 | test("black hole for argument type string", _ => { 29 | let mockFn = JestJs.inferred_fn() 30 | let fn = MockJs.fn(mockFn) 31 | 32 | expect(call(fn, "some string"))->toBeUndefined 33 | }) 34 | 35 | test("calls - records call arguments", _ => { 36 | let mockFn = JestJs.inferred_fn() 37 | let fn = MockJs.fn(mockFn) 38 | 39 | let _ = call(fn, "first") 40 | let _ = call(fn, "second") 41 | let calls = mockFn->MockJs.calls 42 | 43 | expect(calls)->toEqual(["first", "second"]) 44 | }) 45 | 46 | test("instances - sanity check - is empty array", _ => { 47 | let mockFn = JestJs.inferred_fn() 48 | let instances = MockJs.instances(mockFn) 49 | 50 | expect(instances)->toEqual([]) 51 | }) 52 | 53 | test("instances - records created instances", _ => { 54 | let mockFn = JestJs.fn(%raw("function (n) { this.n = n; }")) 55 | 56 | mockFn->MockJs.new1(4)->ignore 57 | mockFn->MockJs.new1(7)->ignore 58 | 59 | let instances = MockJs.instances(mockFn) 60 | 61 | expect(instances)->toEqual([{"n": 4}, {"n": 7}]) 62 | }) 63 | 64 | test("mockClear - resets calls", _ => { 65 | let mockFn = JestJs.inferred_fn() 66 | let fn = MockJs.fn(mockFn) 67 | 68 | let before = mockFn->MockJs.calls 69 | let _ = (call(fn, 1), call(fn, 2)) 70 | let inbetween = mockFn->MockJs.calls 71 | mockFn->MockJs.mockClear 72 | let after = mockFn->MockJs.calls 73 | 74 | expect((before, inbetween, after))->toEqual(([], [1, 2], [])) 75 | }) 76 | 77 | test("mockReset - resets calls", _ => { 78 | let mockFn = JestJs.inferred_fn() 79 | let fn = MockJs.fn(mockFn) 80 | 81 | let before = mockFn->MockJs.calls 82 | let _ = (call(fn, 1), call(fn, 2)) 83 | let inbetween = mockFn->MockJs.calls 84 | mockFn->MockJs.mockReset 85 | let after = mockFn->MockJs.calls 86 | 87 | expect((before, inbetween, after))->toEqual(([], [1, 2], [])) 88 | }) 89 | 90 | test("mockReset - resets implementations", _ => { 91 | let mockFn = JestJs.inferred_fn() 92 | mockFn->MockJs.mockReturnValue(Js.Undefined.return(128))->ignore 93 | let fn = MockJs.fn(mockFn) 94 | 95 | let before = call(fn, ()) 96 | mockFn->MockJs.mockReset 97 | let after = call(fn, ()) 98 | 99 | expect((before, after))->toEqual((Js.Undefined.return(128), Js.Undefined.empty)) 100 | }) 101 | 102 | test("mockImplementation - sets implementation to use for subsequent invocations", _ => { 103 | let mockFn = JestJs.inferred_fn() 104 | let fn = MockJs.fn(mockFn) 105 | 106 | let before = call(fn, 10) 107 | mockFn->MockJs.mockImplementation(a => Js.Undefined.return(string_of_int(a)))->ignore 108 | 109 | expect((before, call(fn, 18), call(fn, 24)))->toEqual(( 110 | Js.Undefined.empty, 111 | Js.Undefined.return("18"), 112 | Js.Undefined.return("24"), 113 | )) 114 | }) 115 | 116 | test("mockImplementationOnce - queues implementation for one subsequent invocation", _ => { 117 | let mockFn = JestJs.inferred_fn() 118 | let fn = MockJs.fn(mockFn) 119 | 120 | let before = call(fn, 10) 121 | mockFn->MockJs.mockImplementationOnce(a => Js.Undefined.return(string_of_int(a)))->ignore 122 | mockFn 123 | ->MockJs.mockImplementationOnce(a => Js.Undefined.return(string_of_int(a * 2))) 124 | ->ignore 125 | 126 | expect((before, call(fn, 18), call(fn, 24), call(fn, 12)))->toEqual(( 127 | Js.Undefined.empty, 128 | Js.Undefined.return("18"), 129 | Js.Undefined.return("48"), 130 | Js.Undefined.empty, 131 | )) 132 | }) 133 | 134 | test("mockReturnThis - returns `this` on subsequent invocations", _ => { 135 | let mockFn = JestJs.inferred_fn() 136 | let this = "this" 137 | let fn = bindThis(mockFn->MockJs.fn, this) 138 | 139 | let before = call(fn, ()) 140 | mockFn->MockJs.mockReturnThis->ignore 141 | 142 | expect((before, call(fn, ()), call(fn, ())))->toEqual(( 143 | Js.Undefined.empty, 144 | Js.Undefined.return(this), 145 | Js.Undefined.return(this), 146 | )) 147 | }) 148 | 149 | test("mockReturnValue - returns given value on subsequent invocations", _ => { 150 | let mockFn = JestJs.inferred_fn() 151 | let fn = MockJs.fn(mockFn) 152 | 153 | let before = call(fn, 10) 154 | mockFn->MockJs.mockReturnValue(Js.Undefined.return(146))->ignore 155 | 156 | expect((before, call(fn, 18), call(fn, 24)))->toEqual(( 157 | Js.Undefined.empty, 158 | Js.Undefined.return(146), 159 | Js.Undefined.return(146), 160 | )) 161 | }) 162 | 163 | test("mockReturnValueOnce - queues implementation for one subsequent invocation", _ => { 164 | let mockFn = JestJs.inferred_fn() 165 | let fn = MockJs.fn(mockFn) 166 | 167 | let before = call(fn, 10) 168 | mockFn->MockJs.mockReturnValueOnce(Js.Undefined.return(29))->ignore 169 | mockFn->MockJs.mockReturnValueOnce(Js.Undefined.return(41))->ignore 170 | 171 | expect((before, call(fn, 18), call(fn, 24), call(fn, 12)))->toEqual(( 172 | Js.Undefined.empty, 173 | Js.Undefined.return(29), 174 | Js.Undefined.return(41), 175 | Js.Undefined.empty, 176 | )) 177 | }) 178 | 179 | /* 180 | Skip.test "bindThis" (fun _ -> 181 | let fn = ((fun a -> string_of_int a) [@bs]) in 182 | let boundFn = bindThis fn "this" in 183 | 184 | expect (call boundFn () 2) -> toEqual "2" 185 | ); 186 | */ 187 | 188 | test("mockClear - resets instances", _ => { 189 | let mockFn = JestJs.fn(%raw("function (n) { this.n = n; }")) 190 | 191 | let before = mockFn->MockJs.instances 192 | 193 | mockFn->MockJs.new1(4)->ignore 194 | mockFn->MockJs.new1(7)->ignore 195 | 196 | let inbetween = mockFn->MockJs.instances 197 | 198 | mockFn->MockJs.mockClear 199 | 200 | let after = mockFn->MockJs.instances 201 | 202 | expect((before, inbetween, after))->toEqual(([], [{"n": 4}, {"n": 7}], [])) 203 | }) 204 | }) 205 | 206 | describe("fn", _ => { 207 | test("calls implementation", _ => { 208 | let mockFn = JestJs.fn(a => string_of_int(a)) 209 | let fn = MockJs.fn(mockFn) 210 | 211 | expect(fn(18))->toBe("18") 212 | }) 213 | 214 | test("calls - records call arguments", _ => { 215 | let mockFn = JestJs.fn(a => string_of_int(a)) 216 | 217 | let _ = MockJs.fn(mockFn)(74) 218 | let _ = MockJs.fn(mockFn)(89435) 219 | let calls = mockFn->MockJs.calls 220 | 221 | expect(calls)->toEqual([74, 89435]) 222 | }) 223 | 224 | test("mockClear - resets calls", _ => { 225 | let mockFn = JestJs.fn(a => string_of_int(a)) 226 | 227 | let before = mockFn->MockJs.calls 228 | let _ = (MockJs.fn(mockFn)(1), MockJs.fn(mockFn)(2)) 229 | let inbetween = mockFn->MockJs.calls 230 | mockFn->MockJs.mockClear 231 | let after = mockFn->MockJs.calls 232 | 233 | expect((before, inbetween, after))->toEqual(([], [1, 2], [])) 234 | }) 235 | 236 | test("mockReset - resets calls", _ => { 237 | let mockFn = JestJs.fn(a => string_of_int(a)) 238 | let fn = MockJs.fn(mockFn) 239 | 240 | let before = mockFn->MockJs.calls 241 | let _ = (fn(1), fn(2)) 242 | let inbetween = mockFn->MockJs.calls 243 | mockFn->MockJs.mockReset 244 | let after = mockFn->MockJs.calls 245 | 246 | expect((before, inbetween, after))->toEqual(([], [1, 2], [])) 247 | }) 248 | 249 | /* TODO: Actually removes the original imlementation too, causing it to return undefined, which usually won't be a valid return value for the function type it mocks */ 250 | Skip.test( 251 | "mockReset - resets implementations - skipped for now as this removes the original implementation too causing an undefnied to be returned", 252 | _ => { 253 | let mockFn = JestJs.fn(a => string_of_int(a)) 254 | mockFn->MockJs.mockReturnValue("128")->ignore 255 | let fn = MockJs.fn(mockFn) 256 | 257 | let before = fn(0) 258 | mockFn->MockJs.mockReset 259 | let after = fn(1) 260 | 261 | expect((before, after))->toEqual(("128", "1")) 262 | }, 263 | ) 264 | 265 | test("mockImplementation - sets implementation to use for subsequent invocations", _ => { 266 | let mockFn = JestJs.fn(a => string_of_int(a)) 267 | let fn = MockJs.fn(mockFn) 268 | 269 | let before = fn(10) 270 | mockFn->MockJs.mockImplementation(a => string_of_int(a * 2))->ignore 271 | 272 | expect((before, fn(18), fn(24)))->toEqual(("10", "36", "48")) 273 | }) 274 | 275 | test("mockImplementationOnce - queues implementation for one subsequent invocation", _ => { 276 | let mockFn = JestJs.fn(a => string_of_int(a)) 277 | let fn = MockJs.fn(mockFn) 278 | 279 | let before = fn(10) 280 | mockFn->MockJs.mockImplementationOnce(a => string_of_int(a * 3))->ignore 281 | mockFn->MockJs.mockImplementationOnce(a => string_of_int(a * 2))->ignore 282 | 283 | expect((before, fn(18), fn(24), fn(12)))->toEqual(("10", "54", "48", "12")) 284 | }) 285 | 286 | /* mockReturnThis doesn't make sense for native functions 287 | test "mockReturnThis - returns `this` on subsequent invocations" (fun _ -> 288 | let mockFn = JestJs.fn (fun a -> string_of_int a) in 289 | let this = "this" in 290 | let fn = bindThis (mockFn -> MockJs.fn) this in 291 | 292 | let before = fn () in 293 | mockFn -> MockJs.mockReturnThis -> ignore; 294 | 295 | expect 296 | (before, fn (), fn ()) 297 | -> toEqual 298 | (Js.Undefined.empty, Js.Undefined.return this, Js.Undefined.return this) 299 | ); 300 | */ 301 | 302 | test("mockReturnValue - returns given value on subsequent invocations", _ => { 303 | let mockFn = JestJs.fn(a => string_of_int(a)) 304 | let fn = MockJs.fn(mockFn) 305 | 306 | let before = fn(10) 307 | mockFn->MockJs.mockReturnValue("146")->ignore 308 | 309 | expect((before, fn(18), fn(24)))->toEqual(("10", "146", "146")) 310 | }) 311 | 312 | test("mockReturnValueOnce - queues implementation for one subsequent invocation", _ => { 313 | let mockFn = JestJs.fn(a => string_of_int(a)) 314 | let fn = MockJs.fn(mockFn) 315 | 316 | let before = fn(10) 317 | mockFn->MockJs.mockReturnValueOnce("29")->ignore 318 | mockFn->MockJs.mockReturnValueOnce("41")->ignore 319 | 320 | expect((before, fn(18), fn(24), fn(12)))->toEqual(("10", "29", "41", "12")) 321 | }) 322 | }) 323 | 324 | describe("fn2", _ => 325 | test("calls implementation", _ => { 326 | let mockFn = JestJs.fn2((a, b) => string_of_int(a + b)) 327 | let fn = MockJs.fn(mockFn) 328 | 329 | expect(call2(fn, 18, 24))->toBe("42") 330 | }) 331 | ) 332 | 333 | /* 334 | test "calls - records call arguments" (fun _ -> 335 | let mockFn = JestJs.fn2 ((fun a b -> string_of_int (a + b)) [@bs]) in 336 | 337 | let _ = MockJs.fn mockFn 18 24 in 338 | let calls = mockFn -> MockJs.calls in 339 | 340 | expect calls -> toEqual [| (18, 24) |] 341 | ); 342 | */ 343 | 344 | describe("MockJs.new", _ => { 345 | test("MockJs.new0", _ => { 346 | let mockFn = JestJs.fn(%raw("function () { this.n = 42; }")) 347 | 348 | let instance = mockFn->MockJs.new0 349 | 350 | expect(instance)->toEqual({"n": 42}) 351 | }) 352 | 353 | test("MockJs.new1", _ => { 354 | let mockFn = JestJs.fn(%raw("function (n) { this.n = n; }")) 355 | 356 | let instance = mockFn->MockJs.new1(4) 357 | 358 | expect(instance)->toEqual({"n": 4}) 359 | }) 360 | 361 | test("MockJs.new2", _ => { 362 | let mockFn = JestJs.fn2(%raw("function (a, b) { this.n = a * b; }")) 363 | 364 | let instance = mockFn->MockJs.new2(4, 7) 365 | 366 | expect(instance)->toEqual({"n": 28}) 367 | }) 368 | }) 369 | } 370 | -------------------------------------------------------------------------------- /__tests__/reason_syntax_test.res: -------------------------------------------------------------------------------- 1 | open Jest 2 | 3 | let () = describe("Reason Syntax", () => { 4 | open Expect 5 | 6 | test("toBe", () => expect(1 + 2)->toBe(3)) 7 | 8 | test("not toBe", () => expect(1 + 2)->not_->toBe(4)) 9 | 10 | test("not_ toBe", () => expect(1 + 2)->not__->toBe(4)) 11 | }) 12 | -------------------------------------------------------------------------------- /__tests__/runner_only_test.res: -------------------------------------------------------------------------------- 1 | module Promise = Js.Promise2 2 | 3 | include Jest.Runner({ 4 | type t<_> = bool 5 | let affirm = ok => assert ok 6 | }) 7 | 8 | let () = { 9 | Only.test("Only.test", () => true) 10 | 11 | Only.testAsync("Only.testAsync", finish => finish(true)) 12 | Only.testAsync("testAsync - timeout ok", ~timeout=1, finish => finish(true)) 13 | 14 | Only.testPromise("Only.testPromise", () => Promise.resolve(true)) 15 | Only.testPromise("testPromise - timeout ok", ~timeout=1, () => Promise.resolve(true)) 16 | 17 | Only.testAll("testAll", list{"foo", "bar", "baz"}, input => Js.String.length(input) === 3) 18 | Only.testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, (( 19 | input, 20 | output, 21 | )) => Js.String.length(input) === output) 22 | 23 | Only.testAllPromise("testAllPromise", list{"foo", "bar", "baz"}, input => 24 | Promise.resolve(Js.String.length(input) === 3) 25 | ) 26 | Only.testAllPromise( 27 | "testAllPromise - tuples", 28 | list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, 29 | ((input, output)) => Promise.resolve(Js.String.length(input) === output), 30 | ) 31 | 32 | Only.describe("Only.describe", () => test("some aspect", () => 1 + 2 === 3)) 33 | } 34 | -------------------------------------------------------------------------------- /__tests__/runner_test.res: -------------------------------------------------------------------------------- 1 | module Promise = Js.Promise2 2 | 3 | include Jest.Runner({ 4 | type t<_> = bool 5 | let affirm = ok => assert ok 6 | }) 7 | 8 | let () = { 9 | test("test", () => true) 10 | 11 | Skip.test("test - expect fail", () => false) 12 | 13 | testAsync("testAsync", finish => finish(true)) 14 | 15 | Skip.testAsync("testAsync - no done", _ => ()) 16 | 17 | Skip.testAsync("testAsync - expect fail", finish => finish(false)) 18 | 19 | testAsync("testAsync - timeout ok", ~timeout=1, finish => finish(true)) 20 | 21 | Skip.testAsync("testAsync - timeout fail", ~timeout=1, _ => ()) 22 | 23 | testPromise("testPromise", () => Promise.resolve(true)) 24 | 25 | Skip.testPromise("testPromise - reject", () => Promise.reject(Failure(""))) 26 | 27 | Skip.testPromise("testPromise - expect fail", () => Promise.resolve(false)) 28 | 29 | testPromise("testPromise - timeout ok", ~timeout=1, () => Promise.resolve(true)) 30 | 31 | Skip.testPromise("testPromise - timeout fail", ~timeout=1, () => 32 | Promise.make((~resolve as _, ~reject as _) => ()) 33 | ) 34 | 35 | testAll("testAll", list{"foo", "bar", "baz"}, input => Js.String.length(input) === 3) 36 | testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, ((input, output)) => 37 | Js.String.length(input) === output 38 | ) 39 | 40 | testAllPromise("testAllPromise", list{"foo", "bar", "baz"}, input => 41 | Promise.resolve(Js.String.length(input) === 3) 42 | ) 43 | testAllPromise("testAllPromise - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, (( 44 | input, 45 | output, 46 | )) => Promise.resolve(Js.String.length(input) === output)) 47 | 48 | describe("describe", () => test("some aspect", () => true)) 49 | 50 | describe("beforeAll", () => { 51 | let x = ref(0) 52 | 53 | beforeAll(() => x := x.contents + 4) 54 | 55 | test("x is 4", () => x.contents === 4) 56 | test("x is still 4", () => x.contents === 4) 57 | }) 58 | 59 | describe("beforeAllAsync", () => { 60 | describe("without timeout", () => { 61 | let x = ref(0) 62 | 63 | beforeAllAsync( 64 | finish => { 65 | x := x.contents + 4 66 | finish() 67 | }, 68 | ) 69 | 70 | test("x is 4", () => x.contents === 4) 71 | test("x is still 4", () => x.contents === 4) 72 | }) 73 | 74 | describe("with 100ms timeout", () => { 75 | let x = ref(0) 76 | 77 | beforeAllAsync( 78 | ~timeout=100, 79 | finish => { 80 | x := x.contents + 4 81 | finish() 82 | }, 83 | ) 84 | 85 | test("x is 4", () => x.contents === 4) 86 | test("x is still 4", () => x.contents === 4) 87 | }) 88 | 89 | Skip.describe("timeout should fail suite", () => { 90 | beforeAllAsync(~timeout=1, _ => ()) 91 | 92 | test("", () => true) /* runner will crash if there's no tests */ 93 | }) 94 | }) 95 | 96 | describe("beforeAllPromise", () => { 97 | describe("without timeout", () => { 98 | let x = ref(0) 99 | 100 | beforeAllPromise( 101 | () => { 102 | x := x.contents + 4 103 | Promise.resolve() 104 | }, 105 | ) 106 | 107 | test("x is 4", () => x.contents === 4) 108 | test("x is still 4", () => x.contents === 4) 109 | }) 110 | 111 | describe("with 100ms timeout", () => { 112 | let x = ref(0) 113 | 114 | beforeAllPromise( 115 | ~timeout=100, 116 | () => { 117 | x := x.contents + 4 118 | Promise.resolve() 119 | }, 120 | ) 121 | 122 | test("x is 4", () => x.contents === 4) 123 | test("x is still 4", () => x.contents === 4) 124 | }) 125 | 126 | Skip.describe("timeout should fail suite", () => { 127 | beforeAllPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 128 | 129 | test("", () => true) /* runner will crash if there's no tests */ 130 | }) 131 | }) 132 | 133 | describe("beforeEach", () => { 134 | let x = ref(0) 135 | 136 | beforeEach(() => x := x.contents + 4) 137 | 138 | test("x is 4", () => x.contents === 4) 139 | test("x is suddenly 8", () => x.contents === 8) 140 | }) 141 | 142 | describe("beforeEachAsync", () => { 143 | describe("without timeout", () => { 144 | let x = ref(0) 145 | 146 | beforeEachAsync( 147 | finish => { 148 | x := x.contents + 4 149 | finish() 150 | }, 151 | ) 152 | 153 | test("x is 4", () => x.contents === 4) 154 | test("x is suddenly 8", () => x.contents === 8) 155 | }) 156 | 157 | describe("with 100ms timeout", () => { 158 | let x = ref(0) 159 | 160 | beforeEachAsync( 161 | ~timeout=100, 162 | finish => { 163 | x := x.contents + 4 164 | finish() 165 | }, 166 | ) 167 | 168 | test("x is 4", () => x.contents === 4) 169 | test("x is suddenly 8", () => x.contents === 8) 170 | }) 171 | 172 | Skip.describe("timeout should fail suite", () => { 173 | beforeEachAsync(~timeout=1, _ => ()) 174 | 175 | test("", () => true) /* runner will crash if there's no tests */ 176 | }) 177 | }) 178 | 179 | describe("beforeEachPromise", () => { 180 | describe("without timeout", () => { 181 | let x = ref(0) 182 | 183 | beforeEachPromise( 184 | () => { 185 | x := x.contents + 4 186 | Promise.resolve(true) 187 | }, 188 | ) 189 | 190 | test("x is 4", () => x.contents === 4) 191 | test("x is suddenly 8", () => x.contents === 8) 192 | }) 193 | 194 | describe("with 100ms timeout", () => { 195 | let x = ref(0) 196 | 197 | beforeEachPromise( 198 | ~timeout=100, 199 | () => { 200 | x := x.contents + 4 201 | Promise.resolve(true) 202 | }, 203 | ) 204 | 205 | test("x is 4", () => x.contents === 4) 206 | test("x is suddenly 8", () => x.contents === 8) 207 | }) 208 | 209 | Skip.describe("timeout should fail suite", () => { 210 | beforeEachPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 211 | 212 | test("", () => true) /* runner will crash if there's no tests */ 213 | }) 214 | }) 215 | 216 | describe("afterAll", () => { 217 | let x = ref(0) 218 | 219 | describe("phase 1", () => { 220 | afterAll(() => x := x.contents + 4) 221 | 222 | test("x is 0", () => x.contents === 0) 223 | test("x is still 0", () => x.contents === 0) 224 | }) 225 | 226 | describe("phase 2", () => test("x is suddenly 4", () => x.contents === 4)) 227 | }) 228 | 229 | describe("afterAllAsync", () => { 230 | describe("without timeout", () => { 231 | let x = ref(0) 232 | 233 | describe( 234 | "phase 1", 235 | () => { 236 | afterAllAsync( 237 | finish => { 238 | x := x.contents + 4 239 | finish() 240 | }, 241 | ) 242 | 243 | test("x is 0", () => x.contents === 0) 244 | test("x is still 0", () => x.contents === 0) 245 | }, 246 | ) 247 | 248 | describe("phase 2", () => test("x is suddenly 4", () => x.contents === 4)) 249 | }) 250 | 251 | describe("with 100ms timeout", () => { 252 | let x = ref(0) 253 | 254 | describe( 255 | "phase 1", 256 | () => { 257 | afterAllAsync( 258 | ~timeout=100, 259 | finish => { 260 | x := x.contents + 4 261 | finish() 262 | }, 263 | ) 264 | 265 | test("x is 0", () => x.contents === 0) 266 | test("x is still 0", () => x.contents === 0) 267 | }, 268 | ) 269 | 270 | describe("phase 2", () => test("x is suddenly 4", () => x.contents === 4)) 271 | }) 272 | 273 | Skip.describe("timeout should fail suite", () => { 274 | afterAllAsync(~timeout=1, _ => ()) 275 | test("", () => true) 276 | }) 277 | }) 278 | 279 | describe("afterAllPromise", () => { 280 | describe("without timeout", () => { 281 | let x = ref(0) 282 | 283 | describe( 284 | "phase 1", 285 | () => { 286 | afterAllPromise( 287 | () => { 288 | x := x.contents + 4 289 | Promise.resolve(true) 290 | }, 291 | ) 292 | 293 | test("x is 0", () => x.contents === 0) 294 | test("x is still 0", () => x.contents === 0) 295 | }, 296 | ) 297 | 298 | describe("phase 2", () => test("x is suddenly 4", () => x.contents === 4)) 299 | }) 300 | 301 | describe("with 100ms timeout", () => { 302 | let x = ref(0) 303 | 304 | describe( 305 | "phase 1", 306 | () => { 307 | afterAllPromise( 308 | ~timeout=100, 309 | () => { 310 | x := x.contents + 4 311 | Promise.resolve(true) 312 | }, 313 | ) 314 | 315 | test("x is 0", () => x.contents === 0) 316 | test("x is still 0", () => x.contents === 0) 317 | }, 318 | ) 319 | 320 | describe("phase 2", () => test("x is suddenly 4", () => x.contents === 4)) 321 | }) 322 | 323 | Skip.describe("timeout should fail suite", () => { 324 | afterAllPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 325 | test("", () => true) 326 | }) 327 | }) 328 | 329 | describe("afterEach", () => { 330 | let x = ref(0) 331 | 332 | afterEach(() => x := x.contents + 4) 333 | 334 | test("x is 0", () => x.contents === 0) 335 | test("x is suddenly 4", () => x.contents === 4) 336 | }) 337 | 338 | describe("afterEachAsync", () => { 339 | describe("without timeout", () => { 340 | let x = ref(0) 341 | 342 | afterEachAsync( 343 | finish => { 344 | x := x.contents + 4 345 | finish() 346 | }, 347 | ) 348 | 349 | test("x is 0", () => x.contents === 0) 350 | test("x is suddenly 4", () => x.contents === 4) 351 | }) 352 | 353 | describe("with 100ms timeout", () => { 354 | let x = ref(0) 355 | 356 | afterEachAsync( 357 | ~timeout=100, 358 | finish => { 359 | x := x.contents + 4 360 | finish() 361 | }, 362 | ) 363 | 364 | test("x is 0", () => x.contents === 0) 365 | test("x is suddenly 4", () => x.contents === 4) 366 | }) 367 | 368 | Skip.describe("timeout should fail suite", () => { 369 | afterEachAsync(~timeout=1, _ => ()) 370 | test("", () => true) 371 | }) 372 | }) 373 | 374 | describe("afterEachPromise", () => { 375 | describe("without timeout", () => { 376 | let x = ref(0) 377 | 378 | afterEachPromise( 379 | () => { 380 | x := x.contents + 4 381 | Promise.resolve(true) 382 | }, 383 | ) 384 | 385 | test("x is 0", () => x.contents === 0) 386 | test("x is suddenly 4", () => x.contents === 4) 387 | }) 388 | 389 | describe("with 100ms timeout", () => { 390 | let x = ref(0) 391 | 392 | afterEachPromise( 393 | ~timeout=100, 394 | () => { 395 | x := x.contents + 4 396 | Promise.resolve(true) 397 | }, 398 | ) 399 | 400 | test("x is 0", () => x.contents === 0) 401 | test("x is suddenly 4", () => x.contents === 4) 402 | }) 403 | 404 | Skip.describe("timeout should fail suite", () => { 405 | afterEachPromise(~timeout=1, () => Promise.make((~resolve as _, ~reject as _) => ())) 406 | test("", () => true) 407 | }) 408 | }) 409 | 410 | describe("Only", () => 411 | /* See runner_only_test.ml */ 412 | () 413 | ) 414 | 415 | describe("Skip", () => { 416 | Skip.test("Skip.test", () => false) 417 | 418 | Skip.testAsync("Skip.testAsync", finish => finish(false)) 419 | Skip.testAsync("Skip.testAsync - timeout", ~timeout=1, _ => ()) 420 | 421 | Skip.testPromise("Skip.testPromise", () => Promise.resolve(false)) 422 | Skip.testPromise("testPromise - timeout", ~timeout=1, () => 423 | Promise.make((~resolve, ~reject as _) => resolve(. false)) 424 | ) 425 | 426 | Skip.testAll("testAll", list{"foo", "bar", "baz"}, _ => false) 427 | Skip.testAll("testAll - tuples", list{("foo", 3), ("barbaz", 6), ("bananas!", 8)}, ((_, _)) => 428 | false 429 | ) 430 | 431 | Skip.describe("Skip.describe", () => test("some aspect", () => false)) 432 | }) 433 | } 434 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', 4 | {targets: {node: 'current'}} 5 | ] 6 | ], 7 | "plugins": [] 8 | } -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@glennsl/rescript-jest", 3 | "namespace": false, 4 | "bsc-flags": ["-bs-no-version-header", "-bs-super-errors"], 5 | "suffix": ".bs.js", 6 | "package-specs": { 7 | "module": "commonjs", 8 | "in-source": true 9 | }, 10 | "sources": [ 11 | { 12 | "dir": "src" 13 | }, 14 | { 15 | "dir": "__tests__", 16 | "type": "dev" 17 | } 18 | ], 19 | "warnings": { 20 | "number": "-44" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | module.exports = { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/cg/d94zwtxd07g_549g2fxfplbm0000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls and instances between every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | coverageProvider: "v8", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | coverageReporters: [ 38 | "json", 39 | "html", 40 | "text", 41 | // "lcov", 42 | // "clover" 43 | ], 44 | 45 | // An object that configures minimum threshold enforcement for coverage results 46 | coverageThreshold: { 47 | "global": { 48 | "branches": 85, 49 | "functions": 85, 50 | "lines": 85, 51 | "statements": 85 52 | } 53 | }, 54 | 55 | // A path to a custom dependency extractor 56 | // dependencyExtractor: undefined, 57 | 58 | // Make calling deprecated APIs throw helpful error messages 59 | // errorOnDeprecated: false, 60 | 61 | // Force coverage collection from ignored files using an array of glob patterns 62 | // forceCoverageMatch: [], 63 | 64 | // A path to a module which exports an async function that is triggered once before all test suites 65 | // globalSetup: undefined, 66 | 67 | // A path to a module which exports an async function that is triggered once after all test suites 68 | // globalTeardown: undefined, 69 | 70 | // A set of global variables that need to be available in all test environments 71 | // globals: {}, 72 | 73 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 74 | // maxWorkers: "50%", 75 | 76 | // An array of directory names to be searched recursively up from the requiring module's location 77 | // moduleDirectories: [ 78 | // "node_modules", 79 | // "" 80 | // ], 81 | 82 | // An array of file extensions your modules use 83 | moduleFileExtensions: [ 84 | "js", 85 | "mjs", 86 | // "jsx", 87 | // "ts", 88 | // "tsx", 89 | // "json", 90 | // "node" 91 | ], 92 | 93 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 94 | // moduleNameMapper: { 95 | // '^react(.*)$': '/node_modules/react$1', 96 | // }, 97 | 98 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 99 | // modulePathIgnorePatterns: [], 100 | 101 | // Activates notifications for test results 102 | // notify: false, 103 | 104 | // An enum that specifies notification mode. Requires { notify: true } 105 | // notifyMode: "failure-change", 106 | 107 | // A preset that is used as a base for Jest's configuration 108 | // preset: undefined, 109 | 110 | // Run tests from one or more projects 111 | // projects: undefined, 112 | 113 | // Use this configuration option to add custom reporters to Jest 114 | // reporters: undefined, 115 | 116 | // Automatically reset mock state between every test 117 | // resetMocks: false, 118 | 119 | // Reset the module registry before running each individual test 120 | // resetModules: false, 121 | 122 | // A path to a custom resolver 123 | // resolver: undefined, 124 | 125 | // Automatically restore mock state between every test 126 | // restoreMocks: false, 127 | 128 | // The root directory that Jest should scan for tests and modules within 129 | // rootDir: undefined, 130 | 131 | // A list of paths to directories that Jest should use to search for files in 132 | // roots: [ 133 | // "" 134 | // ], 135 | 136 | // Allows you to use a custom runner instead of Jest's default test runner 137 | // runner: "jest-runner", 138 | 139 | // The paths to modules that run some code to configure or set up the testing environment before each test 140 | // setupFiles: [], 141 | 142 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 143 | // setupFilesAfterEnv: [ 144 | // "./__tests__/setupTests.js" 145 | // ], 146 | 147 | // The number of seconds after which a test is considered as slow and reported as such in the results. 148 | // slowTestThreshold: 5, 149 | 150 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 151 | // snapshotSerializers: [], 152 | 153 | // The test environment that will be used for testing 154 | //testEnvironment: "jsdom", 155 | 156 | // Options that will be passed to the testEnvironment 157 | // testEnvironmentOptions: {}, 158 | 159 | // Adds a location field to test results 160 | // testLocationInResults: false, 161 | 162 | // The glob patterns Jest uses to detect test files 163 | testMatch: [ 164 | //"**/__tests__/**/*.[jt]s?(x)", 165 | "**/__tests__/**/*_test.mjs", 166 | "**/__tests__/**/*_test.bs.js", 167 | //"**/?(*.)+(spec|test).[tj]s?(x)" 168 | ], 169 | 170 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 171 | // testPathIgnorePatterns: [ 172 | // "/node_modules/" 173 | // ], 174 | 175 | // The regexp pattern or array of patterns that Jest uses to detect test files 176 | // testRegex: [], 177 | 178 | // This option allows the use of a custom results processor 179 | // testResultsProcessor: undefined, 180 | 181 | // This option allows use of a custom test runner 182 | // testRunner: "jest-circus/runner", 183 | 184 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 185 | // testURL: "http://localhost", 186 | 187 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 188 | // timers: "real", 189 | 190 | // A map from regular expressions to paths to transformers 191 | transform: { 192 | "^.+\.m?js$": "babel-jest" 193 | // "^.+\.js$": "babel-jest" 194 | }, 195 | 196 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 197 | transformIgnorePatterns: [ 198 | "node_modules/(?!(rescript)/)" 199 | // "/node_modules/", 200 | // "\\.pnp\\.[^\\/]+$", 201 | // "/src/" 202 | ], 203 | 204 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 205 | // unmockedModulePathPatterns: undefined, 206 | 207 | // Indicates whether each individual test should be reported during the run 208 | verbose: true, 209 | 210 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 211 | // watchPathIgnorePatterns: [], 212 | 213 | // Whether to use watchman for file crawling 214 | // watchman: true, 215 | }; 216 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@glennsl/rescript-jest", 3 | "version": "0.11.0", 4 | "description": "Rescript bindings to the Jest testing framework", 5 | "author": "glennsl", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "rescript build -with-deps", 9 | "clean": "rescript clean", 10 | "start": "rescript build -w", 11 | "test": "npm run build && jest", 12 | "test-ci": "npm run build && jest --coverage --coverageDirectory='coverage'", 13 | "watch:rescript": "rescript build -with-deps -w", 14 | "watch:jest": "jest --coverage --watchAll", 15 | "watch:screen": "screen -c .screenrc" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/glennsl/rescript-jest.git" 20 | }, 21 | "keywords": [ 22 | "Rescript", 23 | "jest", 24 | "test" 25 | ], 26 | "bugs": { 27 | "url": "https://github.com/glennsl/rescript-jest/issues" 28 | }, 29 | "homepage": "https://github.com/glennsl/rescript-jest#readme", 30 | "files": [ 31 | "src/", 32 | "bsconfig.json" 33 | ], 34 | "devDependencies": { 35 | "@babel/core": "^7.15.8", 36 | "@babel/preset-env": "^7.15.8", 37 | "babel-jest": "^27.3.1", 38 | "rescript": "^11.1.0" 39 | }, 40 | "dependencies": { 41 | "jest": "^27.3.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/jest.res: -------------------------------------------------------------------------------- 1 | module Promise = Js.Promise2 2 | 3 | type modifier<'a> = [ 4 | | #Just('a) 5 | | #Not('a) 6 | ] 7 | 8 | let mapMod = (f, x) => 9 | switch x { 10 | | #Just(a) => #Just(f(a)) 11 | | #Not(a) => #Not(f(a)) 12 | } 13 | 14 | type rec assertion = 15 | | Ok: assertion 16 | | Fail(string): assertion 17 | 18 | | ArrayContains(modifier<(array<'a>, 'a)>): assertion 19 | | ArrayContainsEqual(modifier<(array<'a>, 'a)>): assertion 20 | | ArrayLength(modifier<(array<'a>, int)>): assertion 21 | | ArraySuperset(modifier<(array<'a>, array<'a>)>): assertion 22 | | Be(modifier<('a, 'a)>): assertion 23 | | Equal(modifier<('a, 'a)>): assertion 24 | | FloatCloseTo(modifier<(float, float)>): assertion 25 | | FloatSoCloseTo(modifier<(float, float, int)>): assertion 26 | | GreaterThan(modifier<('a, 'a)>): assertion 27 | | GreaterThanOrEqual(modifier<('a, 'a)>): assertion 28 | | LessThan(modifier<('a, 'a)>): assertion 29 | | LessThanOrEqual(modifier<('a, 'a)>): assertion 30 | | StringContains(modifier<(string, string)>): assertion 31 | | StringMatch(modifier<(string, Js.Re.t)>): assertion 32 | 33 | | Throws(modifier _>): assertion 34 | 35 | | MatchInlineSnapshot(_, string): assertion 36 | | MatchSnapshot(_): assertion 37 | | MatchSnapshotName(_, string): assertion 38 | | ThrowsMatchSnapshot(unit => _): assertion 39 | 40 | /* JS */ 41 | | Defined(modifier>): assertion 42 | | Falsy(modifier<'a>): assertion 43 | | Null(modifier>): assertion 44 | | Truthy(modifier<'a>): assertion 45 | | Undefined(modifier>): assertion 46 | | ObjectContains(modifier<({..}, array)>): assertion 47 | | ObjectMatch(modifier<({..}, {..})>): assertion 48 | 49 | module type Asserter = { 50 | type t<'a> 51 | let affirm: t<'a> => unit 52 | } 53 | 54 | /* internal */ 55 | module LLExpect: { 56 | type t<'a> = assertion 57 | let affirm: t<'a> => unit 58 | } = { 59 | type t<'a> = assertion 60 | type specialMatch 61 | 62 | @val external expect: 'a => {..} = "expect" 63 | @val external fail: string => unit = "fail" 64 | @val external arrayContaining: array<'a> => specialMatch = "expect.arrayContaining" 65 | @val external stringContaining: string => specialMatch = "expect.stringContaining" 66 | let objectContaining: array => {..} = %raw(` 67 | function (properties) { 68 | var spec = {}; 69 | properties.forEach(function (property) { 70 | spec[property] = expect.anything(); 71 | }); 72 | return spec; 73 | } 74 | `) 75 | 76 | let affirm = x => 77 | switch x { 78 | | Ok => () 79 | | Fail(message) => fail(message) 80 | 81 | | ArrayContains(#Just(a, b)) => expect(a)["toContain"](b) 82 | | ArrayContains(#Not(a, b)) => expect(a)["not"]["toContain"](b) 83 | | ArrayContainsEqual(#Just(a, b)) => expect(a)["toContainEqual"](b) 84 | | ArrayContainsEqual(#Not(a, b)) => expect(a)["not"]["toContainEqual"](b) 85 | | ArrayLength(#Just(a, l)) => expect(a)["toHaveLength"](l) 86 | | ArrayLength(#Not(a, l)) => expect(a)["not"]["toHaveLength"](l) 87 | | ArraySuperset(#Just(a, b)) => expect(a)["toEqual"](arrayContaining(b)) 88 | | ArraySuperset(#Not(a, b)) => expect(a)["not"]["toEqual"](arrayContaining(b)) 89 | | Be(#Just(a, b)) => expect(a)["toBe"](b) 90 | | Be(#Not(a, b)) => expect(a)["not"]["toBe"](b) 91 | | Equal(#Just(a, b)) => expect(a)["toEqual"](b) 92 | | Equal(#Not(a, b)) => expect(a)["not"]["toEqual"](b) 93 | | FloatCloseTo(#Just(a, b)) => expect(a)["toBeCloseTo"](b) 94 | | FloatCloseTo(#Not(a, b)) => expect(a)["not"]["toBeCloseTo"](b) 95 | | FloatSoCloseTo(#Just(a, b, p)) => expect(a)["toBeCloseTo"](. b, p) 96 | | FloatSoCloseTo(#Not(a, b, p)) => expect(a)["not"]["toBeCloseTo"](. b, p) 97 | | GreaterThan(#Just(a, b)) => expect(a)["toBeGreaterThan"](b) 98 | | GreaterThan(#Not(a, b)) => expect(a)["not"]["toBeGreaterThan"](b) 99 | | GreaterThanOrEqual(#Just(a, b)) => expect(a)["toBeGreaterThanOrEqual"](b) 100 | | GreaterThanOrEqual(#Not(a, b)) => expect(a)["not"]["toBeGreaterThanOrEqual"](b) 101 | | LessThan(#Just(a, b)) => expect(a)["toBeLessThan"](b) 102 | | LessThan(#Not(a, b)) => expect(a)["not"]["toBeLessThan"](b) 103 | | LessThanOrEqual(#Just(a, b)) => expect(a)["toBeLessThanOrEqual"](b) 104 | | LessThanOrEqual(#Not(a, b)) => expect(a)["not"]["toBeLessThanOrEqual"](b) 105 | | StringMatch(#Just(s, re)) => expect(s)["toMatch"](re) 106 | | StringMatch(#Not(s, re)) => expect(s)["not"]["toMatch"](re) 107 | | StringContains(#Just(a, b)) => expect(a)["toEqual"](stringContaining(b)) 108 | | StringContains(#Not(a, b)) => expect(a)["not"]["toEqual"](stringContaining(b)) 109 | 110 | | Throws(#Just(f)) => expect(f)["toThrow"](.) 111 | | Throws(#Not(f)) => expect(f)["not"]["toThrow"](.) 112 | 113 | | MatchInlineSnapshot(a, inlineSnapshot) => expect(a)["toMatchInlineSnapshot"](inlineSnapshot) 114 | | MatchSnapshot(a) => expect(a)["toMatchSnapshot"](.) 115 | | MatchSnapshotName(a, name) => expect(a)["toMatchSnapshot"](name) 116 | | ThrowsMatchSnapshot(f) => expect(f)["toThrowErrorMatchingSnapshot"](.) 117 | 118 | /* JS */ 119 | | Defined(#Just(a)) => expect(a)["toBeDefined"](.) 120 | | Defined(#Not(a)) => expect(a)["not"]["toBeDefined"](.) 121 | | Falsy(#Just(a)) => expect(a)["toBeFalsy"](.) 122 | | Falsy(#Not(a)) => expect(a)["not"]["toBeFalsy"](.) 123 | | Null(#Just(a)) => expect(a)["toBeNull"](.) 124 | | Null(#Not(a)) => expect(a)["not"]["toBeNull"](.) 125 | | Truthy(#Just(a)) => expect(a)["toBeTruthy"](.) 126 | | Truthy(#Not(a)) => expect(a)["not"]["toBeTruthy"](.) 127 | | Undefined(#Just(a)) => expect(a)["toBeUndefined"](.) 128 | | Undefined(#Not(a)) => expect(a)["not"]["toBeUndefined"](.) 129 | | ObjectContains(#Just(a, props)) => expect(a)["toEqual"](objectContaining(props)) 130 | | ObjectContains(#Not(a, props)) => expect(a)["not"]["toEqual"](objectContaining(props)) 131 | | ObjectMatch(#Just(a, b)) => expect(a)["toMatchObject"](b) 132 | | ObjectMatch(#Not(a, b)) => expect(a)["not"]["toMatchObject"](b) 133 | } 134 | } 135 | 136 | module Runner = (A: Asserter) => { 137 | let affirm = A.affirm 138 | @val external _test: (string, @uncurry (unit => Js.undefined)) => unit = "test" 139 | @val 140 | external _testAsync: ( 141 | string, 142 | ((. unit) => unit) => Js.undefined, 143 | Js.Undefined.t, 144 | ) => unit = "test" 145 | @val 146 | external _testPromise: (string, @uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 147 | "test" 148 | 149 | let test = (name, callback) => 150 | _test(name, () => { 151 | affirm(callback()) 152 | Js.undefined 153 | }) 154 | 155 | let testAsync = (name, ~timeout=?, callback) => 156 | _testAsync( 157 | name, 158 | finish => { 159 | callback(case => { 160 | affirm(case) 161 | finish(.) 162 | }) 163 | Js.undefined 164 | }, 165 | Js.Undefined.fromOption(timeout), 166 | ) 167 | 168 | let testPromise = (name, ~timeout=?, callback) => 169 | _testPromise( 170 | name, 171 | () => Promise.then(callback(), a => a->A.affirm->Promise.resolve), 172 | Js.Undefined.fromOption(timeout), 173 | ) 174 | 175 | let testAll = (name, inputs, callback) => List.iter(input => { 176 | let name = `${name} - ${input->Js.String.make}` 177 | _test(name, () => { 178 | affirm(callback(input)) 179 | Js.undefined 180 | }) 181 | }, inputs) 182 | 183 | let testAllPromise = (name: string, inputs, ~timeout=?, callback) => List.iter(input => { 184 | let name = `${name} - ${input->Js.String.make}` 185 | _testPromise( 186 | name, 187 | () => Promise.then(callback(input), a => a->A.affirm->Promise.resolve), 188 | Js.Undefined.fromOption(timeout), 189 | ) 190 | }, inputs) 191 | 192 | @val external describe: (string, @uncurry (unit => Js.undefined)) => unit = "describe" 193 | let describe = (label, f) => 194 | describe(label, () => { 195 | f() 196 | Js.undefined 197 | }) 198 | 199 | @val external beforeAll: (. unit => unit) => unit = "beforeAll" 200 | @val 201 | external beforeAllAsync: (((. unit) => unit) => Js.undefined, Js.Undefined.t) => unit = 202 | "beforeAll" 203 | let beforeAllAsync = (~timeout=?, callback) => beforeAllAsync(finish => { 204 | callback(() => finish(.)) 205 | Js.undefined 206 | }, Js.Undefined.fromOption(timeout)) 207 | @val 208 | external beforeAllPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 209 | "beforeAll" 210 | let beforeAllPromise = (~timeout=?, callback) => 211 | beforeAllPromise(() => Promise.resolve(callback()), Js.Undefined.fromOption(timeout)) 212 | 213 | @val external beforeEach: (. unit => unit) => unit = "beforeEach" 214 | @val 215 | external beforeEachAsync: ( 216 | ((. unit) => unit) => Js.undefined, 217 | Js.Undefined.t, 218 | ) => unit = "beforeEach" 219 | let beforeEachAsync = (~timeout=?, callback) => beforeEachAsync(finish => { 220 | callback(() => finish(.)) 221 | Js.undefined 222 | }, Js.Undefined.fromOption(timeout)) 223 | @val 224 | external beforeEachPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 225 | "beforeEach" 226 | let beforeEachPromise = (~timeout=?, callback) => 227 | beforeEachPromise(() => Promise.resolve(callback()), Js.Undefined.fromOption(timeout)) 228 | 229 | @val external afterAll: (. unit => unit) => unit = "afterAll" 230 | @val 231 | external afterAllAsync: (((. unit) => unit) => Js.undefined, Js.Undefined.t) => unit = 232 | "afterAll" 233 | let afterAllAsync = (~timeout=?, callback) => afterAllAsync(finish => { 234 | callback(() => finish(.)) 235 | Js.undefined 236 | }, Js.Undefined.fromOption(timeout)) 237 | @val 238 | external afterAllPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 239 | "afterAll" 240 | let afterAllPromise = (~timeout=?, callback) => 241 | afterAllPromise(() => Promise.resolve(callback()), Js.Undefined.fromOption(timeout)) 242 | 243 | @val external afterEach: (. unit => unit) => unit = "afterEach" 244 | @val 245 | external afterEachAsync: (((. unit) => unit) => Js.undefined, Js.Undefined.t) => unit = 246 | "afterEach" 247 | let afterEachAsync = (~timeout=?, callback) => afterEachAsync(finish => { 248 | callback(() => finish(.)) 249 | Js.undefined 250 | }, Js.Undefined.fromOption(timeout)) 251 | @val 252 | external afterEachPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 253 | "afterEach" 254 | let afterEachPromise = (~timeout=?, callback) => 255 | afterEachPromise(() => Promise.resolve(callback()), Js.Undefined.fromOption(timeout)) 256 | 257 | module Only = { 258 | @val external _test: (string, @uncurry (unit => Js.undefined)) => unit = "it.only" 259 | @val 260 | external _testAsync: ( 261 | string, 262 | ((. unit) => unit) => Js.undefined, 263 | Js.Undefined.t, 264 | ) => unit = "it.only" 265 | @val 266 | external _testPromise: (string, @uncurry (unit => promise<'a>), Js.Undefined.t) => unit = 267 | "it.only" 268 | 269 | let test = (name, callback) => 270 | _test(name, () => { 271 | affirm(callback()) 272 | Js.undefined 273 | }) 274 | 275 | let testAsync = (name, ~timeout=?, callback) => 276 | _testAsync( 277 | name, 278 | finish => { 279 | callback(assertion => { 280 | affirm(assertion) 281 | finish(.) 282 | }) 283 | Js.undefined 284 | }, 285 | Js.Undefined.fromOption(timeout), 286 | ) 287 | 288 | let testPromise = (name, ~timeout=?, callback) => 289 | _testPromise( 290 | name, 291 | () => Promise.then(callback(), a => a->affirm->Promise.resolve), 292 | Js.Undefined.fromOption(timeout), 293 | ) 294 | 295 | let testAll = (name, inputs, callback) => List.iter(input => { 296 | let name = `${name} - ${input->Js.String.make}` 297 | _test(name, () => { 298 | affirm(callback(input)) 299 | Js.undefined 300 | }) 301 | }, inputs) 302 | 303 | let testAllPromise = (name, inputs, ~timeout=?, callback) => List.iter(input => { 304 | let name = `${name} - ${input->Js.String.make}` 305 | _testPromise( 306 | name, 307 | () => Promise.then(callback(input), a => a->A.affirm->Promise.resolve), 308 | Js.Undefined.fromOption(timeout), 309 | ) 310 | }, inputs) 311 | 312 | @val 313 | external describe: (string, @uncurry (unit => Js.undefined)) => unit = "describe.only" 314 | let describe = (label, f) => 315 | describe(label, () => { 316 | f() 317 | Js.undefined 318 | }) 319 | } 320 | 321 | module Skip = { 322 | @val external test: (string, @uncurry (unit => A.t<'a>)) => unit = "it.skip" 323 | @val external testAsync: (string, (A.t<'a> => unit) => unit) => unit = "it.skip" 324 | let testAsync = (name, ~timeout as _=?, callback) => testAsync(name, callback) 325 | @val 326 | external testPromise: (string, @uncurry (unit => promise>)) => unit = "it.skip" 327 | let testPromise = (name, ~timeout as _=?, callback) => testPromise(name, callback) 328 | let testAll = (name, inputs, callback) => List.iter(input => { 329 | let name = `${name} - ${input->Js.String.make}` 330 | test(name, () => callback(input)) 331 | }, inputs) 332 | let testAllPromise = (name, inputs, ~timeout as _=?, callback) => List.iter(input => { 333 | let name = `${name} - ${input->Js.String.make}` 334 | testPromise(name, () => callback(input)) 335 | }, inputs) 336 | @val 337 | external describe: (string, @uncurry (unit => Js.undefined)) => unit = "describe.skip" 338 | let describe = (label, f) => 339 | describe(label, () => { 340 | f() 341 | Js.undefined 342 | }) 343 | } 344 | 345 | module Todo = { 346 | @val external test: string => unit = "it.todo" 347 | } 348 | } 349 | 350 | include Runner(LLExpect) 351 | 352 | let pass = Ok 353 | let fail = message => Fail(message) 354 | /* 355 | * Not implemented: 356 | * - expect.anything - pointless when there's `option`, `Js.null` etc. 357 | * - expect.any - pointless when you have types, except against < .. > Js.t, but how to implement this? 358 | * - expect.arrayContaining - implement as overloads of `toEqual`, `toBeCalledWith`, `objectContaining` and `toMatchObject` 359 | * - expect.assertions - Not supported. There should be only one assertion per test. 360 | * - expect.objectContaining - implement as separate matcher and overload of `toBeCalledWith` 361 | * - expect.stringContaining - implement as overloads of `toEqual`, `toBeCalledWith`, `objectContaining` and `toMatchObject` 362 | * - expect.stringMatching - implement as overloads of `toEqual`, `toBeCalledWith`, `objectContaining` and `toMatchObject` 363 | */ 364 | 365 | module Expect = { 366 | type plainPartial<'a> = [#Just('a)] 367 | type invertedPartial<'a> = [#Not('a)] 368 | type partial<'a> = modifier<'a> 369 | 370 | let expect = a => #Just(a) 371 | 372 | let expectFn = (f, a) => #Just(() => f(a)) 373 | 374 | let toBe = (b, p) => Be(mapMod(a => (a, p), b)) 375 | /* toHaveBeenCalled* */ 376 | let toBeCloseTo = (b, p) => FloatCloseTo(mapMod(a => (a, p), b)) 377 | let toBeSoCloseTo = (b, ~digits, p) => FloatSoCloseTo(mapMod(a => (a, p, digits), b)) 378 | let toBeGreaterThan = (b, p) => GreaterThan(mapMod(a => (a, p), b)) 379 | let toBeGreaterThanOrEqual = (b, p) => GreaterThanOrEqual(mapMod(a => (a, p), b)) 380 | let toBeLessThan = (b, p) => LessThan(mapMod(a => (a, p), b)) 381 | let toBeLessThanOrEqual = (b, p) => LessThanOrEqual(mapMod(a => (a, p), b)) 382 | @ocaml.doc(" replaces expect.arrayContaining ") 383 | let toBeSupersetOf = (b, p) => ArraySuperset(mapMod(a => (a, p), b)) 384 | let toContain = (b, p) => ArrayContains(mapMod(a => (a, p), b)) 385 | let toContainEqual = (b, p) => ArrayContainsEqual(mapMod(a => (a, p), b)) 386 | @ocaml.doc(" replaces expect.stringContaining ") 387 | let toContainString = (b, p) => StringContains(mapMod(a => (a, p), b)) 388 | let toEqual = (b, p) => Equal(mapMod(a => (a, p), b)) 389 | let toHaveLength = (l, p) => ArrayLength(mapMod(a => (a, p), l)) 390 | let toMatch = (p, s) => StringMatch(mapMod(a => (a, Js.Re.fromString(s)), p)) 391 | let toMatchInlineSnapshot = (#Just(a), inlineSnapshot) => MatchInlineSnapshot(a, inlineSnapshot) 392 | let toMatchRe = (re, p) => StringMatch(mapMod(a => (a, p), re)) 393 | let toMatchSnapshot = (#Just(a)) => MatchSnapshot(a) 394 | let toMatchSnapshotWithName = (#Just(a), name) => MatchSnapshotName(a, name) 395 | let toThrow = f => Throws((f :> modifier<_>)) 396 | let toThrowErrorMatchingSnapshot = (#Just(f)) => ThrowsMatchSnapshot(f) 397 | let not_ = (#Just(a)) => #Not(a) 398 | let not__ = not_ /* For Reason syntax compatibility. TODO: deprecate and remove */ 399 | 400 | module Operators = { 401 | @@ocaml.text(" experimental ") 402 | 403 | let \"==" = (a, b) => toBe(a, b) 404 | let \">" = (a, b) => toBeGreaterThan(a, b) 405 | let \">=" = (a, b) => toBeGreaterThanOrEqual(a, b) 406 | let \"<" = (a, b) => toBeLessThan(a, b) 407 | let \"<=" = (a, b) => toBeLessThanOrEqual(a, b) 408 | let \"=" = (a, b) => toEqual(a, b) 409 | let \"<>" = (a, b) => a->not_->toEqual(b) 410 | let \"!=" = (a, b) => a->not_->toBe(b) 411 | } 412 | } 413 | 414 | module ExpectJs = { 415 | include Expect 416 | 417 | let toBeDefined = a => Defined((a :> modifier<_>)) 418 | let toBeFalsy = a => Falsy((a :> modifier<_>)) 419 | /* toBeInstanceOf */ 420 | let toBeNull = a => Null((a :> modifier<_>)) 421 | let toBeTruthy = a => Truthy((a :> modifier<_>)) 422 | let toBeUndefined = a => Undefined((a :> modifier<_>)) 423 | 424 | @ocaml.doc(" replaces expect.objectContaining ") 425 | let toContainProperties = (props, p) => ObjectContains(mapMod(a => (a, p), props)) 426 | 427 | let toMatchObject = (b, p) => ObjectMatch(mapMod(a => (a, p), b)) 428 | } 429 | 430 | module MockJs = { 431 | @@ocaml.text(" experimental ") 432 | 433 | type fn<'fn, 'args, 'ret> 434 | 435 | %%raw(` 436 | function makeNewMock(self) { 437 | return new (Function.prototype.bind.apply(self, arguments)); 438 | } 439 | `) 440 | 441 | @val external new0: fn 'ret, unit, 'ret> => 'ret = "makeNewMock" 442 | let new0 = new0 443 | @val external new1: (fn<'a => 'ret, 'a, 'ret>, 'a) => 'ret = "makeNewMock" 444 | let new1 = (self, a) => new1(self, a) 445 | @val external new2: (fn<(. 'a, 'b) => 'ret, ('a, 'b), 'ret>, 'a, 'b) => 'ret = "makeNewMock" 446 | let new2 = (self, a, b) => new2(self, a, b) 447 | 448 | external fn: fn<'fn, _, _> => 'fn = "%identity" 449 | @get @scope("mock") external calls: fn<_, 'args, _> => array<'args> = "calls" 450 | let calls = self => 451 | Js.Array.copy(calls(self)) /* Awesome, the bloody things are mutated so we need to copy */ 452 | let calls = self => 453 | Array.map( 454 | %raw(` 455 | function (args) { return args.length === 1 ? args[0] : args } 456 | `), 457 | calls(self), 458 | ) /* there's no such thing as aa 1-ary tuple, so we need to unbox single-element arrays */ 459 | @get @scope("mock") 460 | external instances: fn<_, _, 'ret> => array<'ret> = 461 | "instances" /* TODO: semms this only records "instances" created by `new` */ 462 | let instances = self => 463 | Js.Array.copy(instances(self)) /* Awesome, the bloody things are mutated so we need to copy */ 464 | 465 | @ocaml.doc(" Beware: this actually replaces `mock`, not just `mock.instances` and `mock.calls` ") 466 | @send 467 | external mockClear: fn<'fn, 'a, 'b> => unit = "mockClear" 468 | @send external mockReset: fn<'fn, 'a, 'b> => unit = "mockReset" 469 | @send external mockImplementation: (fn<'fn, 'a, 'b> as 'self, 'fn) => 'self = "mockImplementation" 470 | @send 471 | external mockImplementationOnce: (fn<'fn, _, _> as 'self, 'fn) => 'self = "mockImplementationOnce" 472 | @send 473 | external mockReturnThis: fn<_, _, 'ret> => 'ret = 474 | "mockReturnThis" /* not type safe, we don't know what `this` actually is */ 475 | @send external mockReturnValue: (fn<_, _, 'ret> as 'self, 'ret) => 'self = "mockReturnValue" 476 | @send 477 | external mockReturnValueOnce: (fn<_, _, 'ret> as 'self, 'ret) => 'self = "mockReturnValueOnce" 478 | } 479 | 480 | module Jest = { 481 | type fakeTimerImplementation = [#legacy | #modern] 482 | @val external clearAllTimers: unit => unit = "jest.clearAllTimers" 483 | @val external runAllTicks: unit => unit = "jest.runAllTicks" 484 | @val external runAllTimers: unit => unit = "jest.runAllTimers" 485 | @val external runAllImmediates: unit => unit = "jest.runAllImmediates" 486 | @val external runTimersToTime: int => unit = "jest.runTimersToTime" 487 | @val external advanceTimersByTime: int => unit = "jest.advanceTimersByTime" 488 | @val external runOnlyPendingTimers: unit => unit = "jest.runOnlyPendingTimers" 489 | @val external useFakeTimers: unit => unit = "jest.useFakeTimers" 490 | @val external useFakeTimersImplementation: fakeTimerImplementation => unit = "jest.useFakeTimers" 491 | let useFakeTimers = (~implementation: option=?, ()) => { 492 | switch implementation { 493 | | None => useFakeTimers() 494 | | Some(implString) => useFakeTimersImplementation(implString) 495 | } 496 | } 497 | @val external useRealTimers: unit => unit = "jest.useRealTimers" 498 | 499 | @val external setSystemTimeWithInt: int => unit = "jest.setSystemTime" 500 | @val external setSystemTimeWithDate: Js.Date.t => unit = "jest.setSystemTime" 501 | 502 | type systemTime = [#int(int) | #date(Js.Date.t)] 503 | let setSystemTime = systemTime => 504 | switch systemTime { 505 | | #date(date) => setSystemTimeWithDate(date) 506 | | #int(num) => setSystemTimeWithInt(num) 507 | } 508 | } 509 | 510 | module JestJs = { 511 | @@ocaml.text(" experimental ") 512 | 513 | @val external disableAutomock: unit => unit = "jest.disableAutomock" 514 | @val external enableAutomock: unit => unit = "jest.enableAutomock" 515 | /* genMockFromModule */ 516 | @val external resetModules: unit => unit = "jest.resetModules" 517 | @val 518 | external inferred_fn: unit => MockJs.fn<(. 'a) => Js.undefined<'b>, 'a, Js.undefined<'b>> = 519 | "jest.fn" /* not sure how useful this really is */ 520 | @val external fn: ('a => 'b) => MockJs.fn<'a => 'b, 'a, 'b> = "jest.fn" 521 | @val external fn2: ((. 'a, 'b) => 'c) => MockJs.fn<(. 'a, 'b) => 'c, ('a, 'b), 'c> = "jest.fn" 522 | /* TODO 523 | external fn3 : ('a -> 'b -> 'c -> 'd) -> ('a * 'b * 'c) MockJs.fn = "jest.fn" [@@bs.val] 524 | external fn4 : ('a -> 'b -> 'c -> 'd -> 'e) -> ('a * 'b * 'c * 'd) MockJs.fn = "jest.fn" [@@bs.val] 525 | external fn5 : ('a -> 'b -> 'c -> 'd -> 'e -> 'f) -> ('a * 'a * 'c * 'd * 'e) MockJs.fn = "jest.fn" [@@bs.val] 526 | external fn6 : ('a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'g) -> ('a * 'b * 'c * 'd * 'e * 'f) MockJs.fn = "jest.fn" [@@bs.val] 527 | */ 528 | /* external isMockFunction : MockJs.fn -> Js.boolean = "jest.isMockFunction" [@@bs.val] */ /* pointless with types? */ 529 | @val external mock: string => unit = "jest.mock" 530 | @val external mockWithFactory: (string, unit => 'a) => unit = "jest.mock" 531 | @val external mockVirtual: (string, unit => 'a, {..}) => unit = "jest.mock" 532 | /* TODO If this is merely defined, babel-plugin-jest-hoist fails with "The second argument of `jest.mock` must be a function." Silly thing. 533 | let mockVirtual : string -> (unit -> 'a) -> unit = 534 | fun moduleName factory -> mockVirtual moduleName factory [%bs.obj { _virtual = Js.true_ }] 535 | */ 536 | @val external clearAllMocks: unit => unit = "jest.clearAllMocks" 537 | @val external resetAllMocks: unit => unit = "jest.resetAllMocks" 538 | @val external setMock: (string, {..}) => unit = "jest.setMock" 539 | @val external unmock: string => unit = "jest.unmock" 540 | @val 541 | external spyOn: ({..} as 'this, string) => MockJs.fn = 542 | "jest.spyOn" /* this is a bit too dynamic */ 543 | } 544 | -------------------------------------------------------------------------------- /src/jest.resi: -------------------------------------------------------------------------------- 1 | type assertion 2 | 3 | module type Asserter = { 4 | type t<'a> 5 | let affirm: t<'a> => unit 6 | } 7 | 8 | module Runner: (A: Asserter) => 9 | { 10 | let test: (string, unit => A.t<_>) => unit 11 | let testAsync: (string, ~timeout: int=?, (A.t<_> => unit) => unit) => unit 12 | let testPromise: (string, ~timeout: int=?, unit => promise>) => unit 13 | let testAll: (string, list<'a>, 'a => A.t<_>) => unit 14 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise>) => unit 15 | 16 | let describe: (string, unit => unit) => unit 17 | 18 | @val external beforeAll: (. unit => unit) => unit = "beforeAll" 19 | let beforeAllAsync: (~timeout: int=?, (unit => unit) => unit) => unit 20 | let beforeAllPromise: (~timeout: int=?, unit => promise<'a>) => unit 21 | @val external beforeEach: (. unit => unit) => unit = "beforeEach" 22 | let beforeEachAsync: (~timeout: int=?, (unit => unit) => unit) => unit 23 | let beforeEachPromise: (~timeout: int=?, unit => promise<'a>) => unit 24 | @val external afterAll: (. unit => unit) => unit = "afterAll" 25 | let afterAllAsync: (~timeout: int=?, (unit => unit) => unit) => unit 26 | let afterAllPromise: (~timeout: int=?, unit => promise<'a>) => unit 27 | @val external afterEach: (. unit => unit) => unit = "afterEach" 28 | let afterEachAsync: (~timeout: int=?, (unit => unit) => unit) => unit 29 | let afterEachPromise: (~timeout: int=?, unit => promise<'a>) => unit 30 | 31 | module Only: { 32 | let test: (string, unit => A.t<_>) => unit 33 | let testAsync: (string, ~timeout: int=?, (A.t<_> => unit) => unit) => unit 34 | let testPromise: (string, ~timeout: int=?, unit => promise>) => unit 35 | let testAll: (string, list<'a>, 'a => A.t<_>) => unit 36 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise>) => unit 37 | let describe: (string, unit => unit) => unit 38 | } 39 | 40 | module Skip: { 41 | let test: (string, unit => A.t<_>) => unit 42 | let testAsync: (string, ~timeout: int=?, (A.t<_> => unit) => unit) => unit 43 | let testPromise: (string, ~timeout: int=?, unit => promise>) => unit 44 | let testAll: (string, list<'a>, 'a => A.t<_>) => unit 45 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise>) => unit 46 | let describe: (string, unit => unit) => unit 47 | } 48 | } 49 | 50 | let test: (string, unit => assertion) => unit 51 | let testAsync: (string, ~timeout: int=?, (assertion => unit) => unit) => unit 52 | let testPromise: (string, ~timeout: int=?, unit => promise) => unit 53 | let testAll: (string, list<'a>, 'a => assertion) => unit 54 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise) => unit 55 | 56 | let describe: (string, unit => unit) => unit 57 | 58 | @val external beforeAll: (. unit => unit) => unit = "beforeAll" 59 | let beforeAllAsync: (~timeout: int=?, (unit => unit) => unit) => unit 60 | let beforeAllPromise: (~timeout: int=?, unit => promise<'a>) => unit 61 | @val external beforeEach: (. unit => unit) => unit = "beforeEach" 62 | let beforeEachAsync: (~timeout: int=?, (unit => unit) => unit) => unit 63 | let beforeEachPromise: (~timeout: int=?, unit => promise<'a>) => unit 64 | @val external afterAll: (. unit => unit) => unit = "afterAll" 65 | let afterAllAsync: (~timeout: int=?, (unit => unit) => unit) => unit 66 | let afterAllPromise: (~timeout: int=?, unit => promise<'a>) => unit 67 | @val external afterEach: (. unit => unit) => unit = "afterEach" 68 | let afterEachAsync: (~timeout: int=?, (unit => unit) => unit) => unit 69 | let afterEachPromise: (~timeout: int=?, unit => promise<'a>) => unit 70 | 71 | module Only: { 72 | let test: (string, unit => assertion) => unit 73 | let testAsync: (string, ~timeout: int=?, (assertion => unit) => unit) => unit 74 | let testPromise: (string, ~timeout: int=?, unit => promise) => unit 75 | let testAll: (string, list<'a>, 'a => assertion) => unit 76 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise) => unit 77 | let describe: (string, unit => unit) => unit 78 | } 79 | 80 | module Skip: { 81 | let test: (string, unit => assertion) => unit 82 | let testAsync: (string, ~timeout: int=?, (assertion => unit) => unit) => unit 83 | let testPromise: (string, ~timeout: int=?, unit => promise) => unit 84 | let testAll: (string, list<'a>, 'a => assertion) => unit 85 | let testAllPromise: (string, list<'a>, ~timeout: int=?, 'a => promise) => unit 86 | let describe: (string, unit => unit) => unit 87 | } 88 | 89 | module Todo: { 90 | let test: string => unit 91 | } 92 | 93 | let pass: assertion 94 | let fail: string => assertion 95 | 96 | module Expect: { 97 | type plainPartial<'a> = [#Just('a)] 98 | type invertedPartial<'a> = [#Not('a)] 99 | type partial<'a> = [ 100 | | plainPartial<'a> 101 | | invertedPartial<'a> 102 | ] 103 | 104 | let expect: 'a => plainPartial<'a> 105 | let expectFn: ('a => 'b, 'a) => plainPartial 'b> /* EXPERIMENTAL */ 106 | 107 | let toBe: ([< partial<'a>], 'a) => assertion 108 | let toBeCloseTo: ([< partial], float) => assertion 109 | let toBeSoCloseTo: ([< partial], ~digits: int, float) => assertion 110 | let toBeGreaterThan: ([< partial<'a>], 'a) => assertion 111 | let toBeGreaterThanOrEqual: ([< partial<'a>], 'a) => assertion 112 | let toBeLessThan: ([< partial<'a>], 'a) => assertion 113 | let toBeLessThanOrEqual: ([< partial<'a>], 'a) => assertion 114 | let toBeSupersetOf: ([< partial>], array<'a>) => assertion 115 | let toContain: ([< partial>], 'a) => assertion 116 | let toContainEqual: ([< partial>], 'a) => assertion 117 | let toContainString: ([< partial], string) => assertion 118 | let toEqual: ([< partial<'a>], 'a) => assertion 119 | let toHaveLength: ([< partial>], int) => assertion 120 | let toMatch: ([< partial], string) => assertion 121 | let toMatchInlineSnapshot: (plainPartial<_>, string) => assertion 122 | let toMatchRe: ([< partial], Js.Re.t) => assertion 123 | let toMatchSnapshot: plainPartial<_> => assertion 124 | let toMatchSnapshotWithName: (plainPartial<_>, string) => assertion 125 | let toThrow: [< partial _>] => assertion 126 | let toThrowErrorMatchingSnapshot: plainPartial _> => assertion 127 | 128 | let not_: plainPartial<'a> => invertedPartial<'a> 129 | let not__: plainPartial<'a> => invertedPartial<'a> 130 | 131 | module Operators: { 132 | @@ocaml.text(" experimental ") 133 | 134 | let \"==": ([< partial<'a>], 'a) => assertion 135 | let \">": ([< partial<'a>], 'a) => assertion 136 | let \">=": ([< partial<'a>], 'a) => assertion 137 | let \"<": ([< partial<'a>], 'a) => assertion 138 | let \"<=": ([< partial<'a>], 'a) => assertion 139 | let \"=": ([< partial<'a>], 'a) => assertion 140 | let \"<>": (plainPartial<'a>, 'a) => assertion 141 | let \"!=": (plainPartial<'a>, 'a) => assertion 142 | } 143 | } 144 | 145 | module ExpectJs: { 146 | include module type of Expect 147 | 148 | let toBeDefined: [< partial>] => assertion 149 | let toBeFalsy: [< partial<_>] => assertion 150 | let toBeNull: [< partial>] => assertion 151 | let toBeTruthy: [< partial<_>] => assertion 152 | let toBeUndefined: [< partial>] => assertion 153 | let toContainProperties: ([< partial<{..}>], array) => assertion 154 | let toMatchObject: ([< partial<{..}>], {..}) => assertion 155 | } 156 | 157 | module MockJs: { 158 | @@ocaml.text(" experimental ") 159 | 160 | type fn<'fn, 'args, 'ret> 161 | 162 | let new0: fn 'ret, unit, 'ret> => 'ret 163 | let new1: (fn<'a => 'ret, 'a, 'ret>, 'a) => 'ret 164 | let new2: (fn<(. 'a, 'b) => 'ret, ('a, 'b), 'ret>, 'a, 'b) => 'ret 165 | 166 | external fn: fn<'fn, _, _> => 'fn = "%identity" 167 | let calls: fn<_, 'args, _> => array<'args> 168 | let instances: fn<_, _, 'ret> => array<'ret> 169 | 170 | @ocaml.doc(" Beware: this actually replaces `mock`, not just `mock.instances` and `mock.calls` ") 171 | @send 172 | external mockClear: fn<'fn, 'a, 'b> => unit = "mockClear" 173 | @send external mockReset: fn<'fn, 'a, 'b> => unit = "mockReset" 174 | @send external mockImplementation: (fn<'fn, 'a, 'b> as 'self, 'fn) => 'self = "mockImplementation" 175 | @send 176 | external mockImplementationOnce: (fn<'fn, _, _> as 'self, 'fn) => 'self = "mockImplementationOnce" 177 | @send 178 | external mockReturnThis: fn<_, _, 'ret> => 'ret = 179 | "mockReturnThis" /* not type safe, we don't know what `this` actually is */ 180 | @send external mockReturnValue: (fn<_, _, 'ret> as 'self, 'ret) => 'self = "mockReturnValue" 181 | @send 182 | external mockReturnValueOnce: (fn<_, _, 'ret> as 'self, 'ret) => 'self = "mockReturnValueOnce" 183 | } 184 | 185 | module Jest: { 186 | type fakeTimerImplementation = [#legacy | #modern] 187 | @val external clearAllTimers: unit => unit = "jest.clearAllTimers" 188 | @val external runAllTicks: unit => unit = "jest.runAllTicks" 189 | @val external runAllTimers: unit => unit = "jest.runAllTimers" 190 | @val external runAllImmediates: unit => unit = "jest.runAllImmediates" 191 | @val external runTimersToTime: int => unit = "jest.runTimersToTime" 192 | @val external advanceTimersByTime: int => unit = "jest.advanceTimersByTime" 193 | @val external runOnlyPendingTimers: unit => unit = "jest.runOnlyPendingTimers" 194 | @val external useFakeTimersImplementation: fakeTimerImplementation => unit = "jest.useFakeTimers" 195 | let useFakeTimers: (~implementation: fakeTimerImplementation=?, unit) => unit 196 | @val external useRealTimers: unit => unit = "jest.useRealTimers" 197 | type systemTime = [#int(int) | #date(Js.Date.t)] 198 | let setSystemTime: systemTime => unit 199 | } 200 | 201 | module JestJs: { 202 | @@ocaml.text(" experimental ") 203 | 204 | @val external disableAutomock: unit => unit = "jest.disableAutomock" 205 | @val external enableAutomock: unit => unit = "jest.enableAutomock" 206 | @val external resetModules: unit => unit = "jest.resetModules" 207 | @val 208 | external inferred_fn: unit => MockJs.fn<(. 'a) => Js.undefined<'b>, 'a, Js.undefined<'b>> = 209 | "jest.fn" 210 | @val external fn: ('a => 'b) => MockJs.fn<'a => 'b, 'a, 'b> = "jest.fn" 211 | @val external fn2: ((. 'a, 'b) => 'c) => MockJs.fn<(. 'a, 'b) => 'c, ('a, 'b), 'c> = "jest.fn" 212 | @val external mock: string => unit = "jest.mock" 213 | @val external mockWithFactory: (string, unit => 'a) => unit = "jest.mock" 214 | @val external mockVirtual: (string, unit => 'a, {..}) => unit = "jest.mock" 215 | @val external clearAllMocks: unit => unit = "jest.clearAllMocks" 216 | @val external resetAllMocks: unit => unit = "jest.resetAllMocks" 217 | @val external setMock: (string, {..}) => unit = "jest.setMock" 218 | @val external unmock: string => unit = "jest.unmock" 219 | @val external spyOn: ({..} as 'this, string) => MockJs.fn = "jest.spyOn" 220 | } 221 | --------------------------------------------------------------------------------