{"id":16796865,"url":"https://github.com/alexreardon/raf-stub","last_synced_at":"2025-04-09T14:15:54.746Z","repository":{"id":8718935,"uuid":"59460340","full_name":"alexreardon/raf-stub","owner":"alexreardon","description":"Accurate and predictable testing of requestAnimationFrame and cancelAnimationFrame","archived":false,"fork":false,"pushed_at":"2023-01-04T01:16:53.000Z","size":1313,"stargazers_count":111,"open_issues_count":14,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-02T04:37:57.552Z","etag":null,"topics":["animation","requestanimationframe","stub","testing"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexreardon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-23T07:12:55.000Z","updated_at":"2024-03-20T13:01:59.000Z","dependencies_parsed_at":"2023-01-11T17:27:36.158Z","dependency_job_id":null,"html_url":"https://github.com/alexreardon/raf-stub","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexreardon%2Fraf-stub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexreardon%2Fraf-stub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexreardon%2Fraf-stub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexreardon%2Fraf-stub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexreardon","download_url":"https://codeload.github.com/alexreardon/raf-stub/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248054194,"owners_count":21039952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","requestanimationframe","stub","testing"],"created_at":"2024-10-13T09:20:20.244Z","updated_at":"2025-04-09T14:15:54.719Z","avatar_url":"https://github.com/alexreardon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# raf-stub\n\n[![Build Status](https://travis-ci.org/alexreardon/raf-stub.svg?branch=master)](https://travis-ci.org/alexreardon/raf-stub)\n[![codecov](https://codecov.io/gh/alexreardon/raf-stub/branch/master/graph/badge.svg)](https://codecov.io/gh/alexreardon/raf-stub)\n![npm](https://img.shields.io/npm/dm/raf-stub.svg)\n[![SemVer](https://img.shields.io/badge/SemVer-2.0.0-brightgreen.svg)](http://semver.org/spec/v2.0.0.html)\n\nAccurate and predictable testing of `requestAnimationFrame` and `cancelAnimationFrame`.\n\n**What can `raf-stub` enable you to do?**\n\n- Step through `requestionAnimationFrame` calls one frame at a time\n- Continue to call `requestionAnimationFrame` until there are no frames left. This lets you fast forward to the end of animations.\n- Clear out all animation frames without calling them\n- Control animations that are orchestrated by third party libraries such as [react-motion](https://github.com/chenglou/react-motion)\n- Control time values passed to your `requestAnimationFrame` callbacks\n\nThis is **not** designed to be a polyfill and is only intended for test code.\n\n## Basic usage\n\n```js\n// assuming node running environment so 'global' is the global object\n\nimport createStub from 'raf-stub';\n\nconst render = () =\u003e {\n  return requestAnimationFrame(() =\u003e {\n    console.log('animate allthethings!');\n  });\n};\n\ndescribe('stub', () =\u003e {\n  let stub;\n\n  beforeEach(() =\u003e {\n    stub = createStub();\n    sinon.stub(global, 'requestAnimationFrame', stub.add);\n    sinon.stub(console, 'log');\n  });\n\n  afterEach(() =\u003e {\n    global.requestAnimationFrame.restore();\n  });\n\n  it('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n    render();\n\n    stub.step();\n\n    // console.log =\u003e animate allthethings!\n    expect(console.log.called).to.be(true);\n  });\n\n  it('should allow us to cancel requestAnimationFrame when we want', () =\u003e {\n    const id = render();\n\n    stub.remove(id);\n    stub.step();\n\n    // *crickets*\n    expect(console.log.called).to.be(false);\n  });\n});\n```\n\n### Replace existing `requestAnimationFrame`\n\n```js\nimport { replaceRaf } from 'raf-stub';\n\n// override requestAnimationFrame and cancelAnimationFrame with a stub\nreplaceRaf();\n\nconst render = () =\u003e {\n  return requestAnimationFrame(() =\u003e {\n    console.log('animate allthethings!');\n  });\n};\n\ndescribe('stub', () =\u003e {\n  it('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n    render();\n\n    requestAnimationFrame.step();\n\n    // console.log =\u003e animate allthethings!\n    expect(console.log.called).to.be(true);\n  });\n\n  it('should allow us to cancel requestAnimationFrame when we want', () =\u003e {\n    const id = render();\n\n    cancelAnimationFrame(id);\n    requestAnimationFrame.step();\n\n    // *crickets*\n    expect(console.log.called).to.be(false);\n  });\n});\n```\n\n## Installation\n\n```bash\n## npm\nnpm install raf-stub --save-dev\n\n## yarn\nyarn add raf-stub --dev\n```\n\n## stub\n\nCreated by `createStub()`\n\n**type signature**\n\n```js\ntype Stub = {|\n  add: (cb: Function) =\u003e number,\n  remove: (id: number) =\u003e void,\n  flush: (duration?: number) =\u003e void,\n  reset: () =\u003e void,\n  step: (steps?: number, duration?: number) =\u003e void,\n|};\n```\n\nAn isolated mock that contains it's own state. Each `stub` is independent and have it's own state.\n\n**Note** changing the time values (`startTime`, `frameDuration` and `duration`) do not actually impact how long your test takes to execute, nor does it attach itself to the system clock. It is simply a way for you to have control over the first argument (`currentTime`) to `requestAnimationFrame` callbacks.\n\n### `createStub()`\n\n**type signature**\n\n```js\nfunction createStub (frameDuration: number = 1000 / 60, startTime: number = performance.now()): Stub\n```\n\n**Basic usage**\n\n```js\nconst stub = createStub();\n```\n\n**Advanced usage**\n\n```js\nconst frameDuration = (1000 / 60) * 2; // an extra slow frame\nconst startTime = performance.now() + 1000;\nconst stub = createStub(frameDuration, startTime);\n```\n\n### `stub.add(callback)`\n\nIt schedules the callback to be called in the next frame.\nIt returns an `id` that can be used to cancel the frame in the future. Same api as `requestAnimationFrame`.\n\nCallbacks will _not_ automatically get called after a period of time. You need to explicitly release it using `stub.step()` or `stub.flush()`\n\n**type signature**\n\n```js\nfunction add (cb: Function): number\n```\n\n```js\nconst stub = createStub();\nconst callback = () =\u003e {};\n\nstub.add(callback);\n```\n\n### `stub.remove(id)`\n\nIt takes the id of a `stub.add()` call and cancels it without calling it. Same api as `cancelAnimationFrame(id)`.\n\n**type signature**\n\n```js\nfunction remove (id: number): void\n```\n\n```js\nconst stub = createStub();\nconst callback = () =\u003e console.log('hi');\n\nconst id = stub.add(callback);\n\nstub.remove(id);\n\n// callback is not called as it is no longer queued\nstub.step();\n\n// *crickets*\n```\n\n### `.step()`\n\nExecutes all callbacks in the current frame and optionally additional frames.\n\n**type signature**\n\n```js\nstep: (steps?: number, duration?: ?number) =\u003e void\n```\n\n- `steps` =\u003e the amount of animation frames you would like to release. Defaults to `1`. This is useful when you have nested calls.\n- `duration (Number)` =\u003e the amount of time the frame takes to execute. The default `duration` value is provided by the `frameDuration` argument to `createStub(frameDuration)`. However, you can override it for a specific `.step()` call using the `duration` argument.\n\n**Simple example**\n\n```js\nconst callback1 = () =\u003e console.log('first callback');\nconst callback2 = () =\u003e console.log('second callback');\nconst stub = createStub();\n\nstub.add(callback1);\nstub.add(callback2);\nstub.step();\n\n// console.log =\u003e 'first callback'\n// console.log =\u003e 'second callback'\n```\n\n**Nested example**\n\nSome times calls to `requestAnimationFrame` themselves call `requestAnimationFrame`. `step()` will let you step through them one at a time.\n\n```js\n// this example will use the 'replaceRaf' syntax as it is a little clearer\nconst callback = () =\u003e {\n  console.log('first callback');\n\n  // second frame\n  requestAnimationFrame(() =\u003e console.log('second callback'));\n};\n\nrequestAnimationFrame(callback);\n\n// release the first frame\nrequestAnimationFrame.step();\n\n// console.log =\u003e 'first callback'\n\n// release the second frame\nrequestAnimationFrame.step();\n\n// console.log =\u003e 'second callback'\n```\n\n**Time manipulated example**\n\n```js\nconst startTime = performance.now();\nconst frameDuration = 10;\nconst longFrameDuration = frameDuration * 2;\nconst stub = createStub(frameDuration, startTime);\nconst callback = currentTime =\u003e\n  console.log(`call time: ${startTime - currentTime}`);\n\nstub.add(callback);\nstub.step(1, longFrameDuration);\n\n// console.log =\u003e call time: 20\n```\n\n### `stub.flush()`\n\nExecutes all `requestAnimationFrame` callbacks, including nested calls. It will keep executing frames until there are no frames left. An easy way to to think of this function is \"`step()` until there are no more steps left.\n\n**type signature**\n\n```js\nflush: (duration: ?number) =\u003e void\n```\n\n- `duration` =\u003e the duration for each frame in the flush - each frame gets the same value. If you want different frames to get different values then use `.step()`. The default `duration` value is provided by the `frameDuration` argument to `createStub(frameDuration)`. However, you can override it for a specific `.flush()` call using the `duration` argument.\n\n**Warning** if your code just calls `requestAnimationFrame` in an infinite loop then this will never end. Consider using `.step()` for this use case\n\n**Simple example**\n\n```js\n// this example will use the 'replaceRaf' syntax as it is a little clearer\n\nconst callback = () =\u003e {\n  console.log('first callback');\n\n  // second frame\n  requestAnimationFrame(() =\u003e console.log('second callback'));\n};\n\nrequestAnimationFrame(callback);\napi.flush();\n\n// console.log =\u003e 'first callback'\n// console.log =\u003e 'second callback'\n```\n\n**Time manipulated example**\n\n```js\nconst startTime = performance.now();\nconst stub = createStub(100, startTime);\nconst callback = currentTime =\u003e\n  console.log(`call time: ${currentTime - startTime}`);\n\nstub.add(callback);\nstub.flush(200);\n\n// console.log =\u003e 'call time: 200'\n```\n\n### `.reset()`\n\nClears all the frames without executing any callbacks, unlike `flush()` which executes all the callbacks. Reverts the stub to it's initial state. This is similar to `remove(id)` but it does not require an `id`; `reset` will also clear **all** callbacks in the frame whereas `remove(id)` only removes a single one.\n\n**type signature**\n\n```js\nreset: () =\u003e void\n```\n\n```js\nconst callback = () =\u003e console.log('first callback');\n\nrequestAnimationFrame(callback);\napi.reset();\n\n// callback has been removed so this will do nothing\napi.step();\n\n// *crickets*\n```\n\n## replaceRaf\n\n### `replaceRaf()`\n\nThis function is used to set overwrite `requestAnimationFrame` and `cancelAnimationFrame` on a root (eg `window`). This is useful if you want to control `requestAnimationFrame` for dependencies.\n\n**type signature**\n\n```js\ntype ReplaceRafOptions = {\n    frameDuration?: number,\n    startTime?: number\n};\n\nfunction replaceRaf(roots?: Object[], options?: ?ReplaceRafOptions): void;\n```\n\n- `roots` =\u003e an optional array of roots to be stubbed (eg [`window`, `global`]). If no root is provided then the function will automatically figure out whether to use `window` or `global`\n- `options` =\u003e optional additional values to control the stub. These values are passed as the `frameDuration` and `startTime` arguments to `createStub()`\n\n`options`\n\n- `startTime` =\u003e see `createStub()`\n- `frameDuration` =\u003e see `createStub()`\n\n#### Basic usage\n\n```js\nimport { replaceRaf } from 'raf-stub';\n\nconst root = {}; // could be window, global etc.\nreplaceRaf([root]);\n\n// can let multiple roots share the one stub\n// useful for when you testing environment uses `global`\n// but some libraries may use `window`\n\nreplaceRaf([window, global]);\n\n// if called with no arguments it will use 'window' in the browser and 'global' in node\nreplaceRaf();\n\n// you can override the frameDuration and startTime for the stub\nreplaceRaf([window], {\n  frameDuration: 200,\n  startTime: performance.now() + 1000,\n});\n```\n\nAfter calling `replaceRaf` a root it's `requestAnimationFrame` and `cancelAnimationFrame` functions have been set and given new capabilities.\n\n```js\n// assuming running in node so 'global' is the global rather than 'window'\nimport { replaceRaf } from 'raf-stub';\n\nreplaceRaf(global);\n\nconst callback = () =\u003e alert('hi');\n\n// existing browser api mapped to stub.add\nconst id = requestAnimationFrame(callback);\n\n// existing browser api mapped to stub.remove\ncancelAnimationFrame(id);\n\n// step - see stub.step\nrequestAnimationFrame.step();\n\n// flush - see stub.flush\nrequestAnimationFrame.flush();\n\n// reset - see stub.reset\nrequestAnimationFrame.reset();\n```\n\nSee **stub** for api documentation on `step()`, `flush()` and `reset()`.\n\n### Disclaimers!\n\n- Each call to `replaceRaf` will add a new stub to the `root`. If you want to have the same stub on multiple `roots` then pass them in at the same time (eg `replaceRaf([window, global])`).\n- If you do a one time setup of `replaceRaf()` in a test setup file you will remember to clear the stub after each test.\n\n```js\nrequestAnimationFrame.reset();\n```\n\n## ES5 / ES6\n\n#### ES6 syntax:\n\n```js\nimport stub, { replaceRaf } from 'raf-stub';\n```\n\n#### ES5 syntax (compatible with node.js `require`);\n\n```js\nvar stub = require('raf-stub').default;\nvar replaceRaf = require('raf-stub').replaceRaf;\n```\n\n## Flow types\n\nThis library uses and publishes [flow types](https://flowtype.org/). This ensures internal API consistency and also provides a great consumption story. If your project is using flow types then you can get type checking for all of your `raf-stub` calls, as well as auto complete depending on your editor.\n\n## Semantic Versioning\n\nThis project used [Semantic versioning 2.0.0](http://semver.org/) to ensure a consistent versioning strategy.\n\n`X.Y.Z` (major, minor, patch)\n\n- `X`: breaking changes\n- `Y`: new features (non breaking)\n- `Z`: patches\n\nA safe `raf-stub` `package.json` dependency would therefore be anything that allows changes to the _minor_ or _patch_ version\n\n## Frame `currentTime` precision warning\n\nWhen you a frame is called by `.step()` or `.flush()` it [is given the `currentTime` as the first argument](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).\n\n```js\nconst stub = createStub();\nconst callback = currentTime =\u003e console.log(`the time is ${currentTime}`);\n\nstub.add(callback);\nstub.step();\n\n// console.log('the current time is 472759.63');\n```\n\nBy default `frameDuration` is `1000 / 60` and startTime is `performance.now`. Both of these numbers are ugly decimals (eg `16.6666667`). When they are added together in `.step()` or `.flush()` this can cause [known precision issues in JavaScript](https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch2.md#small-decimal-values). You can find some further discussion about it's impact [here](https://github.com/alexreardon/raf-stub/issues/42).\n\n**Work arounds**\nIf you want to assert the current time inside of a callback - be sure to _add_ the expected time values:\n\n```js\nconst frameDuration = 1000 / 60;\nconst startTime = performance.now();\nconst stub = createStub(frameDuration, startTime);\nconst child = sinon.stub();\nconst parent = sinon.stub().returns(child);\n\nstub.add(callback);\nstub.step();\nstub.step();\n\n// okay\nexpect(parent.calledWith(startTime + frameDuration)).to.be.true;\n\n// not okay - will not mimic precision issues\n// doing this can lead to flakey tests\nexpect(child.calledWith(startTime + 2 * frameDuration)).to.be.true;\n\n// okay\nexpect(child.calledWith(startTime + frameDuration + frameDuration));\n```\n\nAnother simple option is to use integers for **both** `frameDuration` and `startTime`.\n\n```js\nconst frameDuration = 16;\nconst startTime = 100;\nconst stub = createStub(frameDuration, startTime);\nconst child = sinon.stub();\nconst parent = sinon.stub().returns(child);\n\nstub.add(callback);\nstub.step();\nstub.step();\n\n// okay\nexpect(parent.calledWith(startTime + frameDuration)).to.be.true;\n\n// okay\nexpect(child.calledWith(startTime + 2 * frameDuration)).to.be.true;\n\n// okay\nexpect(child.calledWith(startTime + frameDuration + frameDuration));\n```\n\n## Recipes\n\n## `frameDuration` and `startTime`\n\nThe first argument to a `requestAnimationFrame` callback is a [DOMHighResTimeStamp](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp)\n\n```js\nrequestAnimationFrame(currentTime =\u003e {\n  console.log('the current time is', currentTime);\n});\n```\n\nUnder normal circumstances you would not want to modify the default values. Being able to manipulate the `startTime` and `endTime` will let you test code that does some logic based on the `currentTime` argument.\n\n**Note** changing the time values does not actually impact how long your test takes to execute, nor does it attach itself to the system clock. It is simply a way for you to have control over the first argument (`currentTime`) to `requestAnimationFrame` callbacks.\n\n```js\nconst idealFrameDuration = 1000 / 2;\nconst slowFrameDuration = idealFrameDuration * 2;\nconst startTime = performance.now();\nconst stub = createStub(slowFrameDuration, startTime);\n\nstub.add(currentTime =\u003e {\n  if (startTime + currentTime \u003e idealFrameDuration) {\n    console.log('a slow frame occured');\n  } else {\n    console.log('a standard frame occured');\n  }\n});\n\nstub.step();\n\n// console.log =\u003e 'a slow frame occured'\n```\n\n**Controlling the `frameDuration` and `startTime`:**\n\n```js\nconst callback = currentTime =\u003e\n  console.log(`time taken: ${currentTime - startTime}`);\n\n// this will set the frameDuration and startTime for all stub calls. They can be overwritted with specific function calls\nconst stub = createStub(100, performance.now());\n\nstub.add(callback);\n// this will use the values specific when creating the stub\nstub.step();\n// console.log =\u003e 'time taken: 100'\n\nstub.add(callback);\n// this will overwrite the duration of the frame to '200' for this call\nstub.step(1, 200);\n// console.log =\u003e 'time taken: 200'\n\nstub.add(callback);\nstub.flush();\n// console.log =\u003e 'time taken: 100'\n\nstub.add(callback);\nstub.flush(200);\n// console.log =\u003e 'time taken: 200'\n```\n\n### Library dependency\n\nLet's say you use a library that uses request animation frame\n\n```js\n// library.js\nconst ponyfill = callback =\u003e setTimeout(callback, 1000 / 60);\nconst raf = requestAnimationFrame || ponyfill;\n\nexport default function() {\n  raf(() =\u003e {\n    console.log('render allthethings!');\n  });\n}\n```\n\nThe trouble with this is that the library uses a local variable `raf`. This reference is declared when the modules are importing. This means that `raf` will always points to the reference it has when the module is imported for the first time.\n\nThe following **will not work**\n\n```js\n// test.js\nimport createStub from 'raf-stub';\nimport render from 'library';\n\ndescribe('app', () =\u003e {\n  const stub = createStub();\n\n  beforeEach(() =\u003e {\n    sinon.stub(global, 'requestAnimationFrame', stub.add);\n    sinon.stub(console, 'log');\n  });\n\n  it('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n    render();\n\n    stub.step();\n\n    // *crickets* - not executed! :(\n    expect(console.log.called).to.be(true); // failure\n  });\n});\n```\n\nThis won't work when:\n\n- `requestAnimationFrame` **does exist**\n\n```js\nraf === requestAnimationFrame;\n```\n\nThis is because doing `sinon.stub(global, 'requestAnimationFrame', stub.add)` will change the reference that `requestAnimationFrame` points to. What that means is that the library when it calls `raf` will call the original `requestAnimationFrame` and not your stub\n\n- `requestAnimationFrame` **does not exist**\n\n```js\nraf === ponyfill;\n```\n\nIf the ponyfill is being used then we cannot override the reference to `raf` as it is not exposed. Stubbing `requestAnimationFrame` will not help because the library uses a reference to the ponyfill.\n\n#### How can we get this working?\n\n`replaceRaf` to the rescue!\n\nbefore any of your tests code is executed, including module imports, then take the opportunity to set up your stub!\n\n```js\n// test-setup.js\n// your test setup will be running before babel so I will write valid node code.\nvar createStub = require('raf-stub').default;\n\n// option 1: setup a stub yourself\nvar stub = createStub();\nrequestAnimationFrame = stub.add;\nrequestAnimationFrame = stub.remove;\n\n// add additional helpers to requestAnimationFrame:\nObject.assign(requestAnimationFrame, {\n  step: stub.step,\n  flush: stub.flush,\n  reset: stub.reset,\n});\n\n// option 2: use replaceRaf! (this does option1 for you)\nrequire('raf-stub').replaceRaf();\n```\n\nThen everything will work as expected!\n\n```js\n// test.js\nimport render from 'library';\n\ndescribe('app', () =\u003e {\n  const stub = createStub();\n\n  afterEach(() =\u003e {\n    requestAnimationFrame.reset();\n  });\n\n  it('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n    render();\n\n    requestAnimationFrame.step();\n\n    // console.log =\u003e 'render allthethings!'\n    expect(console.log.called).to.be(true);\n  });\n});\n```\n\n### Full `mocha` end-to-end setup\n\nFor when you need to make a stub which can be used by a library (or module that ponyfills `requestAnimationFrame` at compile time)\n\n**package.json**\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"mocha render.test.js --presets es2015 --require test-setup.js\"\n  }\n}\n```\n\n1. `mocha test.js` our test file\n2. `--presets es2015` this will let us use es6 + es6 modules in our test files\n3. `--require test-setup.js` this is our file where we will setup our stub\n\n**test-setup.js**\n\n```js\n// using node require as the babel tranform is not applied to this file\nrequire('raf-stub').replaceRaf();\n```\n\n**render.js**\n\n```js\nconst ponyfill = callback =\u003e setTimeout(callback, 1000 / 60);\nconst raf = requestAnimationFrame || ponyfill;\n\nexport default function() {\n  raf(() =\u003e console.log('done'));\n}\n```\n\n**render.test.js**\n\n```js\nimport render from './render';\n\ndescribe('render', () =\u003e {\n  it('should log to the console', () =\u003e {\n    render();\n\n    requestAnimationFrame.step();\n\n    // console.log =\u003e 'done';\n    expect(console.log.called).to.be(true);\n  });\n});\n```\n\n**command line**\n\n```\nnpm test\n```\n\n## Tests for `raf-stub`\n\nTo run the tests for this library simply execute:\n\n```\nnpm install\nnpm test\n```\n\n## Rationale for library\n\nLet's say you wanted to test some code that uses `requestAnimationFrame`. How would you do it? Here is an example that uses `sinon` and `setTimeout`. You do not need to use `sinon` but it lets you write sequential code rather than needing to use nested timeouts.\n\n```js\ndescribe('app', () =\u003e {\n  let clock;\n\n  beforeEach(() =\u003e {\n    clock = sinon.useFakeTimers();\n    sinon.stub(window, 'requestAnimationFrame', setTimeout);\n    sinon.stub(window, 'cancelAnimationFrame', clearTimeout);\n  });\n\n  afterEach(() =\u003e {\n    clock.restore();\n    window.requestAnimationFrame.restore();\n    window.cancelAnimationFrame.restore();\n  });\n\n  it('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n    const callback = () =\u003e console.log('success');\n\n    requestAnimationFrame(callback);\n\n    // fast forward set timeout\n    clock.tick();\n\n    // console.log =\u003e 'success'\n    expect(console.log.called).to.be(true);\n  });\n});\n```\n\nWe are all good right? Well sort of. For this basic example `setTimeout` was sufficient. Let's have a look at a few more cases where `setTimeout` is not ideal.\n\n_The following examples will assume the same setup as the above example unless otherwise specified_\n\n### Case 1: mixture of `setTimeout` and `requestAnimationFrame`\n\n```js\nit('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n  const callback = () =\u003e {\n    setTimeout(() =\u003e {\n      console.log('success');\n    });\n  };\n\n  requestAnimationFrame(callback);\n\n  // fast forward requestAnimationFrame\n  clock.tick();\n\n  // fast forward setTimeout\n  clock.tick();\n\n  // console.log =\u003e 'success'\n  expect(console.log.called).to.be(true);\n});\n```\n\nBecause both `setTimeout` and `requestAnimationFrame` use `setTimeout` the way we **step** through an animation frame is the same way we **step** through a timeout. This can become hard to reason about in larger functions where there may be a large combination of `setTimeout` and `requestAnimationFrame` code. Having a shared mechanism is prone to misunderstandings. This is solved by `stub.step()` and `stub.flush()`\n\n### Case 2: nested requestAnimationFrames\n\n```js\nit('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n  const callback = () =\u003e {\n    requestAnimationFrame(() =\u003e {\n      console.log('success');\n    });\n  };\n\n  requestAnimationFrame(callback);\n\n  // fast forward requestAnimationFrame\n  clock.tick();\n\n  // fast forward requestAnimationFrame\n  clock.tick();\n\n  // console.log =\u003e 'success'\n  expect(console.log.called).to.be(true);\n});\n```\n\nThis was not too hard. But something to notice is that the nest code is identicial to the previous example. We are relying on comments to understand what is going on.\n\nLet's go a bit deeper:\n\n```js\nit('should allow us to execute requestAnimationFrame when we want', () =\u003e {\n  const render = (iterations = 0) =\u003e {\n    if (iterations \u003e 100 * Math.random()) {\n      return console.log('done');\n    }\n    return requestAnimationFrame(() =\u003e {\n      render(iterations + 1);\n    });\n  };\n\n  render();\n\n  // step through the animation frames\n  clock.tick(100000);\n\n  // console.log =\u003e 'success'\n  expect(console.log.called).to.be(true);\n});\n```\n\nThe problem we have here is that we do not know exactly how many times `render` will call `requestAnimationFrame`. To get around this we just tick the `clock` forward some really large amount. This feels like a hack. Also, you might use a number that is big enough in some circumstances but not others. This can lead to flakey tests. This is solved by `stub.flush()`\n\n### Case 3: `setTimeout` leakage\n\n```js\nit('should log to the console', () =\u003e {\n  const callback = () =\u003e {\n    console.log('test 1: first frame');\n    // requests another frame\n    requestAnimationFrame(() =\u003e {\n      console.log('test 1: second frame');\n    });\n  };\n\n  requestAnimationFrame(callback);\n  clock.tick();\n\n  // console.log =\u003e 'test 1: first frame'\n  expect(console.log.calledOnce).to.be(true);\n  // note the second frame was not cleared\n});\n\nit('should also log to the console', () =\u003e {\n  const callback = () =\u003e console.log('test 2: first frame');\n\n  requestAnimationFrame(callback);\n  clock.tick();\n\n  // console.log =\u003e 'test 1: second frame' -\u003e leaked from first test\n  // console.log =\u003e 'test 2: first frame'\n  expect(console.log.calledOnce).to.be(true); // failure\n});\n```\n\nWhat happened here? We did not clear out all of the`requestAnimationFrame`'s in the original test and they leaked into our second test. We need to `reset` the `setTimeout` queue before running `test 2`.\n\n```js\ndescribe('app', () =\u003e {\n  let clock;\n\n  beforeEach(() =\u003e {\n    clock = sinon.useFakeTimers();\n    sinon.stub(window, 'requestAnimationFrame', setTimeout);\n    sinon.stub(window, 'cancelAnimationFrame', clearTimeout);\n  });\n\n  afterEach(() =\u003e {\n    // need to reset the clock (this actually flushes the clock)\n    clock.tick(1000000);\n\n    // console.log =\u003e 'test 1: second frame'\n\n    clock.restore();\n    window.requestAnimationFrame.restore();\n    window.cancelAnimationFrame.restore();\n  });\n\n  it('test 1', () =\u003e {\n    const callback = () =\u003e {\n      console.log('test 1: first frame');\n      requestAnimationFrame(() =\u003e {\n        console.log('test 1: second frame');\n      });\n    };\n\n    requestAnimationFrame(callback);\n\n    // console.log =\u003e 'test 1: first frame'\n    expect(console.log.called).to.be(true);\n\n    // note the second frame was not cleared\n  });\n\n  it('test 2', () =\u003e {\n    const callback = () =\u003e console.log('test 2: first frame');\n\n    requestAnimationFrame(callback);\n\n    // console.log =\u003e 'test 1: second frame'\n    expect(console.log.calledOnce).to.be(true); // now passing\n  });\n});\n```\n\nWe got around this issue by flushing the clock. We did this by calling `clock.tick(some big number)`. This suffers from the problem that existed a previous example: you cannot be sure that you have actually emptied the queue. You might have noticed a strange `console.log` in the `afterEach` function. This is because when you emptied the `setTimeout` queue with `clock.tick` all of the callbacks executed. In some cases it might lead to unintended consequences. `stub.reset()` allows us to empty a queue **without** needing to execute any of the callbacks.\n\n### Case 4: controlling the first argument to `requestAnimationFrame` callbacks\n\nLets say you have setup that looks like this:\n\n```js\nconst idealFrameDuration = 1000 / 60;\n\nconst startAnimation = startTime =\u003e {\n  let previousTime = startTime;\n\n  const loop = currentTime =\u003e {\n    if (!currentTime) {\n      throw new Error('could not get the current time');\n    }\n\n    const diff = currentTime - previousTime;\n    if (diff \u003e idealFrameDuration) {\n      console.log('a slow frame occurred');\n    } else {\n      console.log('a normal frame occurred');\n    }\n\n    previousTime = currentTime;\n    requestAnimationFrame(loop);\n  };\n\n  requestAnimationFrame(loop);\n};\n\nstartAnimation(performance.now());\n```\n\nHow could we test the behaviour of the `loop` function? Let's try with `setTimeout`\n\n```js\ndescribe('startAnimation', () =\u003e {\n  let clock;\n\n  beforeEach(() =\u003e {\n    clock = sinon.useFakeTimers();\n    sinon.stub(window, 'requestAnimationFrame', setTimeout);\n    sinon.stub(window, 'cancelAnimationFrame', clearTimeout);\n  });\n\n  afterEach(() =\u003e {\n    clock.restore();\n    window.requestAnimationFrame.restore();\n    window.cancelAnimationFrame.restore();\n  });\n\n  it('should pass the updated time to loop', () =\u003e {\n    const startTime = performance.now();\n    startAnimation(startTime);\n\n    clock.tick();\n\n    // Error =\u003e 'could not get the current time'\n  });\n});\n```\n\nWhat happened here? By default `setTimeout` does not pass any argument as the first parameter to callbacks. Getting this to work is hard because we would need to both use `setTimeout` as a replacement for `requestAnimationFrame` as well as changing it's behaviour to pass a controlled value as the first argument.\n\nHow can `raf-stub` help us?\n\n```js\nimport { replaceRaf } from 'raf-stub';\nconst startTime = performance.now();\nconst idealFrameDuration = 1000 / 60;\n\nreplaceRaf([], { startTime });\n\ndescribe('startAnimation', () =\u003e {\n  afterEach(() =\u003e {\n    requestAnimationFrame.reset();\n  });\n\n  it('should pass the updated time to loop', () =\u003e {\n    const slowFrameDuration = idealFrameDuration * 2;\n    startAnimation(startTime);\n\n    requestAnimationFrame.step(1, idealFrameDuration);\n    // console.log =\u003e 'a normal frame occurred'\n    expect(console.log.calledOnce).to.be(true);\n\n    requestAnimationFrame.step(1, slowFrameDuration);\n    // console.log =\u003e 'a slow frame occurred'\n    expect(console.log.calledTwice).to.be(true);\n  });\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexreardon%2Fraf-stub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexreardon%2Fraf-stub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexreardon%2Fraf-stub/lists"}