├── dist
├── backburner
│ ├── binary-search.d.ts
│ ├── interfaces.d.ts
│ ├── iterator-drain.d.ts
│ ├── platform.d.ts
│ ├── utils.d.ts
│ ├── queue.d.ts
│ └── deferred-action-queues.d.ts
├── tests
│ ├── index.html
│ └── qunit.css
├── bench
│ └── index.html
├── loader.js
└── backburner.d.ts
├── .gitignore
├── server
└── index.js
├── lib
└── backburner
│ ├── interfaces.ts
│ ├── iterator-drain.ts
│ ├── binary-search.ts
│ ├── platform.ts
│ ├── utils.ts
│ ├── deferred-action-queues.ts
│ └── queue.ts
├── .npmignore
├── .vscode
├── settings.json
├── tasks.json
└── launch.json
├── .editorconfig
├── tsconfig.json
├── .github
└── workflows
│ └── ci.yml
├── testem.js
├── tests
├── index.html
├── utils
│ └── mock-stable-error.ts
├── index.ts
├── build-next-test.ts
├── bb-has-timers-test.ts
├── defer-iterable-test.ts
├── defer-debug-info-test.ts
├── debug-test.ts
├── async-stack-test.ts
├── queue-test.ts
├── queue-debug-info-test.ts
├── multi-turn-test.ts
├── configurable-timeout-test.ts
├── events-test.ts
├── run-test.ts
├── join-test.ts
├── debug-info-test.ts
├── defer-test.ts
├── cancel-test.ts
├── autorun-test.ts
├── defer-once-test.ts
├── queue-push-unique-test.ts
├── later-test.ts
└── throttle-test.ts
├── tslint.json
├── bench
├── index.html
├── index.js
├── benches
│ ├── later-cancel.js
│ ├── debounce-cancel.js
│ ├── throttle-cancel.js
│ ├── schedule-flush.js
│ └── schedule-cancel.js
└── browser-bench.js
├── CHANGELOG.md
├── LICENSE
├── package.json
├── RELEASE.md
├── README.md
└── ember-cli-build.js
/dist/backburner/binary-search.d.ts:
--------------------------------------------------------------------------------
1 | export default function binarySearch(time: any, timers: any): number;
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | bower_components
4 | tmp/
5 | dist/
6 | npm-debug.log
7 | .vscode/chrome
8 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | module.exports = function(app) {
2 | app.get('/', function(req, res) {
3 | res.redirect('/tests/?hidepassed');
4 | })
5 | };
6 |
--------------------------------------------------------------------------------
/lib/backburner/interfaces.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface IQueueItem {
3 | method: string;
4 | target: Object;
5 | args: Object[];
6 | stack: string | undefined;
7 | }
8 |
--------------------------------------------------------------------------------
/dist/backburner/interfaces.d.ts:
--------------------------------------------------------------------------------
1 | export interface IQueueItem {
2 | method: string;
3 | target: Object;
4 | args: Object[];
5 | stack: string | undefined;
6 | }
7 |
--------------------------------------------------------------------------------
/dist/backburner/iterator-drain.d.ts:
--------------------------------------------------------------------------------
1 | export interface Iterable {
2 | next: () => {
3 | done: boolean;
4 | value?: any;
5 | };
6 | }
7 | export default function (fn: () => Iterable): void;
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | bower_components/
4 | /tmp/
5 | ember-cli-build.js
6 | testem.json
7 | TODOs
8 | /bin/
9 | /config/
10 | /lib/
11 | tests/
12 | .travis.yml
13 | .npmignore
14 | .gitignore
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.git": true,
4 | "**/.DS_Store": true,
5 | ".vscode/chrome": true,
6 | "tmp": true,
7 | "config": true
8 | },
9 | "editor.insertSpaces": true,
10 | "editor.tabSize": 2
11 | }
12 |
--------------------------------------------------------------------------------
/dist/backburner/platform.d.ts:
--------------------------------------------------------------------------------
1 | export interface IPlatform {
2 | setTimeout(fn: Function, ms: number): any;
3 | clearTimeout(id: any): void;
4 | next(): any;
5 | clearNext(): void;
6 | now(): number;
7 | }
8 | export declare function buildNext(flush: () => void): () => void;
9 | export declare function buildPlatform(flush: () => void): IPlatform;
10 |
--------------------------------------------------------------------------------
/lib/backburner/iterator-drain.ts:
--------------------------------------------------------------------------------
1 | // accepts a function that when invoked will return an iterator
2 | // iterator will drain until completion
3 | export interface Iterable {
4 | next: () => { done: boolean, value?: any };
5 | }
6 |
7 | export default function(fn: () => Iterable) {
8 | let iterator = fn();
9 | let result = iterator.next();
10 |
11 | while (result.done === false) {
12 | result.value();
13 | result = iterator.next();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.js]
16 | indent_style = space
17 | indent_size = 2
18 |
19 | [*.{diff,md}]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "target": "es2015",
5 | "noEmit": true,
6 | "baseUrl": ".",
7 | "inlineSourceMap": true,
8 | "inlineSources": true,
9 | "module": "es2015",
10 | "strictNullChecks": true,
11 | "declaration": true,
12 | "paths": {
13 | "backburner": ["lib/index.ts"]
14 | }
15 | },
16 | "files": [
17 | "lib/index.ts",
18 | "tests/index.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: 14
19 | - run: yarn install --frozen-lockfile
20 | - run: yarn problems
21 | - run: yarn lint
22 | - run: yarn test
23 | - run: yarn bench
24 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "0.1.0",
5 | "command": "npm",
6 | "isShellCommand": true,
7 | "suppressTaskName": true,
8 | "tasks": [
9 | {
10 | "taskName": "build",
11 | "args": ["run", "build"],
12 | "isBuildCommand": true
13 | }, {
14 | "taskName": "serve",
15 | "args": ["run", "serve"],
16 | "isBackground": true
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/dist/backburner/utils.d.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 | export declare const QUEUE_ITEM_LENGTH = 5;
3 | export declare const TIMERS_OFFSET = 7;
4 | export declare function isCoercableNumber(suspect: any): boolean;
5 | export declare function getOnError(options: any): any;
6 | export declare function findItem(target: any, method: any, collection: any): number;
7 | export declare function findTimerItem(target: any, method: any, collection: any): number;
8 | export declare function getQueueItems(items: any[], queueItemLength: number, queueItemPositionOffset?: number): IQueueItem[];
9 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | framework: 'qunit',
3 | test_page: 'tests/index.html',
4 | launch_in_ci: ['Chrome'],
5 | launch_in_dev: ['Chrome'],
6 | browser_args: {
7 | Chrome: {
8 | ci: [
9 | // --no-sandbox is needed when running Chrome inside a container
10 | process.env.CI ? '--no-sandbox' : null,
11 | '--headless',
12 | '--disable-gpu',
13 | '--disable-dev-shm-usage',
14 | '--disable-software-rasterizer',
15 | '--mute-audio',
16 | '--remote-debugging-port=0',
17 | '--window-size=1440,900'
18 | ].filter(Boolean)
19 | }
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backburner.js Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/dist/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backburner.js Tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/tests/utils/mock-stable-error.ts:
--------------------------------------------------------------------------------
1 |
2 | const ERROR = Error;
3 | // @ts-ignore - Skip preventing overriding the readonly Error object
4 | Error = ERROR;
5 | let stacks: string[] = [];
6 |
7 | export function pushStackTrace(stackLine: string) {
8 | stacks.push(stackLine);
9 |
10 | return stackLine;
11 | }
12 |
13 | export function overrideError(_Error) {
14 | // @ts-ignore
15 | Error = _Error;
16 | }
17 |
18 | export function resetError() {
19 | // @ts-ignore
20 | Error = ERROR;
21 | stacks = [];
22 | }
23 |
24 | export default class MockStableError {
25 | constructor(public message: string) {}
26 |
27 | get stack(): string {
28 | return stacks.pop() || '';
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/index.ts:
--------------------------------------------------------------------------------
1 | import './async-stack-test';
2 | import './autorun-test';
3 | import './bb-has-timers-test';
4 | import './build-next-test';
5 | import './cancel-test';
6 | import './configurable-timeout-test';
7 | import './debounce-test';
8 | import './debug-info-test';
9 | import './debug-test';
10 | import './defer-debug-info-test';
11 | import './defer-iterable-test';
12 | import './defer-once-test';
13 | import './defer-test';
14 | import './events-test';
15 | import './join-test';
16 | import './later-test';
17 | import './multi-turn-test';
18 | import './queue-debug-info-test';
19 | import './queue-push-unique-test';
20 | import './queue-test';
21 | import './run-test';
22 | import './throttle-test';
23 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "quotemark": [true, "single", "avoid-escape"],
5 | "trailing-comma": [false],
6 | "object-literal-shorthand": false,
7 | "object-literal-sort-keys": false,
8 | "only-arrow-functions": [false],
9 | "jsdoc-format": false,
10 | "prefer-for-of": false,
11 | "no-empty": false,
12 | "no-string-literal": false,
13 | "prefer-const": false,
14 | "max-line-length": [false],
15 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"],
16 | "ban-types": {}
17 | },
18 | "jsRules": {
19 | "quotemark": [true, "single", "avoid-escape"],
20 | "trailing-comma": [false]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/backburner/binary-search.ts:
--------------------------------------------------------------------------------
1 | import { TIMERS_OFFSET } from './utils';
2 |
3 | export default function binarySearch(time, timers) {
4 | let start = 0;
5 | let end = timers.length - TIMERS_OFFSET;
6 | let middle;
7 | let l;
8 |
9 | while (start < end) {
10 | // since timers is an array of pairs 'l' will always
11 | // be an integer
12 | l = (end - start) / TIMERS_OFFSET;
13 |
14 | // compensate for the index in case even number
15 | // of pairs inside timers
16 | middle = start + l - (l % TIMERS_OFFSET);
17 |
18 | if (time >= timers[middle]) {
19 | start = middle + TIMERS_OFFSET;
20 | } else {
21 | end = middle;
22 | }
23 | }
24 |
25 | return (time >= timers[start]) ? start + TIMERS_OFFSET : start;
26 | }
27 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug 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 | "name": "Launch localhost",
9 | "type": "chrome",
10 | "request": "launch",
11 | "url": "http://localhost:4200/tests/",
12 | "userDataDir": "${workspaceRoot}/.vscode/chrome",
13 | "sourceMaps": true,
14 | "webRoot": "${workspaceRoot}/dist",
15 | "sourceMapPathOverrides": {
16 | "../lib/*.ts": "${workspaceRoot}/lib/*.ts",
17 | "../tests/*.ts": "${workspaceRoot}/tests/*.ts"
18 | }
19 | }
20 | ]
21 | }
--------------------------------------------------------------------------------
/bench/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backburner.js Benchmarks
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/dist/bench/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backburner.js Benchmarks
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/build-next-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/build-next', function() {
4 |
5 | QUnit.test('can build custom flushing next', function(assert) {
6 | let done = assert.async();
7 | let next = Backburner.buildNext(() => assert.step('custom next'));
8 |
9 | assert.step('start');
10 | Promise.resolve().then(() => assert.step('first promise resolved'));
11 | next();
12 | Promise.resolve().then(() => assert.step('second promise resolved'));
13 | assert.step('end');
14 |
15 | setTimeout(() => {
16 | assert.verifySteps([
17 | 'start',
18 | 'end',
19 | 'first promise resolved',
20 | 'custom next',
21 | 'second promise resolved',
22 | ]);
23 | done();
24 | }, 10);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/bench/index.js:
--------------------------------------------------------------------------------
1 | var glob = require('glob');
2 | var path = require('path');
3 | var bench = require('do-you-even-bench');
4 |
5 | // to run all (default):
6 | //
7 | // node ./bench/index.js
8 | //
9 | // to run a single benchmark, you can do:
10 | //
11 | // node ./bench/index.js ./bench/benches/some-file.js
12 | var fileGlob = './bench/benches/*.js';
13 | if (process.argv[2]) {
14 | fileGlob = process.argv[2];
15 | console.log(fileGlob);
16 | }
17 |
18 | globalThis.Backburner = require("../dist/backburner").default;
19 |
20 | var suites = [];
21 | glob.sync(fileGlob).forEach(function(file) {
22 | var exported = require( path.resolve( file ) );
23 | if (Array.isArray(exported)) {
24 | suites = suites.concat(exported);
25 | } else {
26 | suites.push(exported);
27 | }
28 | });
29 |
30 | bench(suites);
31 |
--------------------------------------------------------------------------------
/bench/benches/later-cancel.js:
--------------------------------------------------------------------------------
1 | function sharedSetup() {
2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
3 |
4 | var target = {
5 | someMethod: function() { }
6 | };
7 | }
8 |
9 | module.exports = [
10 | {
11 | name: 'Later & Cancel - function',
12 |
13 | Backburner: Backburner,
14 |
15 | setup: sharedSetup,
16 |
17 | fn: function() {
18 | var timer = backburner.later(null, target.someMethod, 100);
19 | backburner.cancel(timer);
20 | }
21 | },
22 | {
23 | name: 'Later & Cancel - function, target',
24 |
25 | Backburner: Backburner,
26 |
27 | setup: sharedSetup,
28 |
29 | fn: function() {
30 | var timer = backburner.later(target, 'someMethod', 100);
31 | backburner.cancel(timer);
32 | }
33 | }
34 | ];
35 |
--------------------------------------------------------------------------------
/bench/browser-bench.js:
--------------------------------------------------------------------------------
1 | import bench from "do-you-even-bench";
2 | import Benchmark from "benchmark";
3 | window.Benchmark = Benchmark;
4 |
5 | import DebounceCancel from "./benches/debounce-cancel";
6 | import LaterCancel from "./benches/later-cancel";
7 | import ScheduleCancel from "./benches/schedule-cancel";
8 | import ScheduleFlush from "./benches/schedule-flush";
9 | import ThrottleCancel from "./benches/throttle-cancel";
10 |
11 | let suites = [
12 | DebounceCancel,
13 | LaterCancel,
14 | ScheduleCancel,
15 | ScheduleFlush,
16 | ThrottleCancel,
17 | ].flat();
18 |
19 | const searchParams = new URLSearchParams(window.location.search);
20 | const filter = searchParams.get("filter");
21 |
22 | if (filter) {
23 | document.querySelector("input[name=filter]").value = filter;
24 | suites = suites.filter((s) =>
25 | s.name.toLowerCase().includes(filter.toLowerCase())
26 | );
27 | }
28 |
29 | bench(suites);
30 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v2.7.0 (2021-04-30)
2 |
3 | #### :rocket: Enhancement
4 | * [#383](https://github.com/BackburnerJS/backburner.js/pull/383) Export types ([@wagenet](https://github.com/wagenet))
5 |
6 | #### :memo: Documentation
7 | * [#390](https://github.com/BackburnerJS/backburner.js/pull/390) Remove Downloads section from README ([@rwjblue](https://github.com/rwjblue))
8 |
9 | #### :house: Internal
10 | * [#391](https://github.com/BackburnerJS/backburner.js/pull/391) Add basic release automation setup. ([@rwjblue](https://github.com/rwjblue))
11 | * [#388](https://github.com/BackburnerJS/backburner.js/pull/388) Re-roll yarn.lock ([@rwjblue](https://github.com/rwjblue))
12 | * [#382](https://github.com/BackburnerJS/backburner.js/pull/382) Upgrade development dependencies ([@wagenet](https://github.com/wagenet))
13 |
14 | #### Committers: 3
15 | - Cyrille David ([@dcyriller](https://github.com/dcyriller))
16 | - Peter Wagenet ([@wagenet](https://github.com/wagenet))
17 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
18 |
19 |
20 | # Changelog
21 |
--------------------------------------------------------------------------------
/tests/bb-has-timers-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/bb-has-timers');
4 |
5 | QUnit.test('hasTimers', function(assert) {
6 | let done = assert.async();
7 | let bb = new Backburner(['ohai']);
8 | let timer;
9 | let target = {
10 | fn() { }
11 | };
12 |
13 | bb.schedule('ohai', null, () => {
14 | assert.ok(!bb.hasTimers(), 'Initially there are no timers');
15 |
16 | timer = bb.later('ohai', () => {});
17 | assert.ok(bb.hasTimers(), 'hasTimers checks timers');
18 |
19 | bb.cancel(timer);
20 |
21 | assert.ok(!bb.hasTimers(), 'Timers are cleared');
22 |
23 | timer = bb.debounce(target, 'fn', 200);
24 | assert.ok(bb.hasTimers(), 'hasTimers checks debouncees');
25 |
26 | bb.cancel(timer);
27 | assert.ok(!bb.hasTimers(), 'Timers are cleared');
28 |
29 | timer = bb.throttle(target, 'fn', 200);
30 | assert.ok(bb.hasTimers(), 'hasTimers checks throttlers');
31 |
32 | bb.cancel(timer);
33 | assert.ok(!bb.hasTimers(), 'Timers are cleared');
34 |
35 | done();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Erik Bryn and contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/dist/backburner/queue.d.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 | export declare const enum QUEUE_STATE {
3 | Pause = 1
4 | }
5 | export default class Queue {
6 | private name;
7 | private globalOptions;
8 | private options;
9 | private _queueBeingFlushed;
10 | private targetQueues;
11 | private index;
12 | private _queue;
13 | constructor(name: string, options?: any, globalOptions?: any);
14 | stackFor(index: any): any;
15 | consoleTaskFor(index: any, inQueueBeingFlushed?: boolean): any;
16 | flush(sync?: Boolean): QUEUE_STATE.Pause | undefined;
17 | hasWork(): boolean;
18 | cancel({ target, method }: {
19 | target: any;
20 | method: any;
21 | }): boolean;
22 | push(target: any, method: any, args: any, stack: any, consoleTask: any): {
23 | queue: Queue;
24 | target: any;
25 | method: any;
26 | };
27 | pushUnique(target: any, method: any, args: any, stack: any, consoleTask: any): {
28 | queue: Queue;
29 | target: any;
30 | method: any;
31 | };
32 | _getDebugInfo(debugEnabled: boolean): IQueueItem[] | undefined;
33 | private invoke;
34 | private invokeWithOnError;
35 | }
36 |
--------------------------------------------------------------------------------
/dist/backburner/deferred-action-queues.d.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 | import Queue, { QUEUE_STATE } from './queue';
3 | export interface IDebugInfo {
4 | [key: string]: IQueueItem[] | undefined;
5 | }
6 | export default class DeferredActionQueues {
7 | queues: {
8 | [name: string]: Queue;
9 | };
10 | queueNameIndex: number;
11 | private queueNames;
12 | constructor(queueNames: string[] | undefined, options: any);
13 | /**
14 | * @method schedule
15 | * @param {String} queueName
16 | * @param {Any} target
17 | * @param {Any} method
18 | * @param {Any} args
19 | * @param {Boolean} onceFlag
20 | * @param {Any} stack
21 | * @return queue
22 | */
23 | schedule(queueName: string, target: any, method: any, args: any, onceFlag: boolean, stack: any, consoleTask: any): {
24 | queue: Queue;
25 | target: any;
26 | method: any;
27 | };
28 | /**
29 | * DeferredActionQueues.flush() calls Queue.flush()
30 | *
31 | * @method flush
32 | * @param {Boolean} fromAutorun
33 | */
34 | flush(fromAutorun?: boolean): QUEUE_STATE.Pause | undefined;
35 | /**
36 | * Returns debug information for the current queues.
37 | *
38 | * @method _getDebugInfo
39 | * @param {Boolean} debugEnabled
40 | * @returns {IDebugInfo | undefined}
41 | */
42 | _getDebugInfo(debugEnabled: boolean): IDebugInfo | undefined;
43 | }
44 |
--------------------------------------------------------------------------------
/bench/benches/debounce-cancel.js:
--------------------------------------------------------------------------------
1 | function sharedSetup() {
2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
3 |
4 | var target = {
5 | someMethod: function() { },
6 | anotherMethod: function() { }
7 | };
8 |
9 | var timer1 = null;
10 | var timer2 = null;
11 | }
12 |
13 | module.exports = [
14 | {
15 | name: 'Debounce - function',
16 |
17 | Backburner: Backburner,
18 |
19 | setup: sharedSetup,
20 |
21 | fn: function() {
22 | backburner.debounce(target.someMethod, 50);
23 | backburner.debounce(target.anotherMethod, 100);
24 | timer1 = backburner.debounce(target.someMethod, 50);
25 | timer2 = backburner.debounce(target.anotherMethod, 100);
26 | },
27 |
28 | teardown: function() {
29 | backburner.cancel(timer1);
30 | backburner.cancel(timer2);
31 | }
32 | },
33 | {
34 | name: 'Debounce & Cancel - function, target',
35 |
36 | Backburner: Backburner,
37 |
38 | setup: sharedSetup,
39 |
40 | fn: function() {
41 | backburner.debounce(target, 'someMethod', 50);
42 | backburner.debounce(target, 'anotherMethod', 100);
43 | timer1 = backburner.debounce(target, 'someMethod', 50);
44 | timer2 = backburner.debounce(target, 'anotherMethod', 100);
45 |
46 | backburner.cancel(timer1);
47 | backburner.cancel(timer2);
48 | },
49 |
50 | teardown: function() {
51 | timer1 = null
52 | timer2 = null
53 | }
54 | }
55 | ];
56 |
--------------------------------------------------------------------------------
/bench/benches/throttle-cancel.js:
--------------------------------------------------------------------------------
1 | function sharedSetup() {
2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
3 |
4 | var target = {
5 | someMethod: function() { },
6 | anotherMethod: function() { }
7 | };
8 |
9 | var timer1 = null;
10 | var timer2 = null;
11 | }
12 |
13 | module.exports = [
14 | {
15 | name: 'Throttle - function',
16 |
17 | Backburner: Backburner,
18 |
19 | setup: sharedSetup,
20 |
21 | fn: function() {
22 | backburner.throttle(target.someMethod, 50);
23 | backburner.throttle(target.anotherMethod, 100);
24 | timer1 = backburner.throttle(target.someMethod, 50);
25 | timer2 = backburner.throttle(target.anotherMethod, 100);
26 | },
27 |
28 | teardown: function() {
29 | backburner.cancel(timer1);
30 | backburner.cancel(timer2);
31 | timer1 = null
32 | timer2 = null
33 | }
34 | },
35 | {
36 | name: 'Throttle & Cancel - function, target',
37 |
38 | Backburner: Backburner,
39 |
40 | setup: sharedSetup,
41 |
42 | fn: function() {
43 | backburner.throttle(target, 'someMethod', 50);
44 | backburner.throttle(target, 'anotherMethod', 100);
45 | timer1 = backburner.throttle(target, 'someMethod', 50);
46 | timer2 = backburner.throttle(target, 'anotherMethod', 100);
47 |
48 | backburner.cancel(timer1);
49 | backburner.cancel(timer2);
50 | },
51 |
52 | teardown: function() {
53 | timer1 = null
54 | timer2 = null
55 | }
56 | }
57 | ];
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@discourse/backburner.js",
3 | "version": "2.7.1-0",
4 | "description": "A fork of backburner.js",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/discourse/backburner.js.git"
8 | },
9 | "license": "MIT",
10 | "author": "Erik Bryn",
11 | "main": "dist/backburner.js",
12 | "jsnext:main": "dist/backburner.js",
13 | "types": "dist/backburner.d.ts",
14 | "scripts": {
15 | "bench": "ember build && node ./bench/index.js",
16 | "build": "ember build --environment=production",
17 | "lint": "tslint --project tsconfig.json",
18 | "problems": "tsc -p tsconfig.json --noEmit",
19 | "prepare": "npm run build",
20 | "serve": "ember serve",
21 | "test": "ember test",
22 | "test:server": "ember test --server"
23 | },
24 | "devDependencies": {
25 | "@types/qunit": "^2.11.1",
26 | "broccoli-funnel": "^3.0.4",
27 | "broccoli-merge-trees": "^4.2.0",
28 | "broccoli-rollup": "^4.1.1",
29 | "broccoli-typescript-compiler": "^7.0.0",
30 | "do-you-even-bench": "^1.0.5",
31 | "ember-cli": "^3.26.1",
32 | "ember-cli-dependency-checker": "^3.2.0",
33 | "ember-cli-inject-live-reload": "2.0.2",
34 | "glob": "^7.1.6",
35 | "loader.js": "^4.7.0",
36 | "lodash": "^4.17.21",
37 | "lolex": "^6.0.0",
38 | "qunit": "^2.14.1",
39 | "release-it": "^14.2.1",
40 | "release-it-lerna-changelog": "^3.1.0",
41 | "rollup-plugin-buble": "^0.19.8",
42 | "rollup-plugin-commonjs": "^10.1.0",
43 | "rollup-plugin-node-resolve": "^5.2.0",
44 | "tslint": "^6.1.3"
45 | },
46 | "publishConfig": {
47 | "registry": "https://registry.npmjs.org",
48 | "access": "public"
49 | },
50 | "release-it": {}
51 | }
52 |
--------------------------------------------------------------------------------
/tests/defer-iterable-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/defer-iterable');
4 |
5 | class Iterator {
6 | private _collection: Function[];
7 | private _iteration: number = 0;
8 | constructor(collection: Function[]) {
9 | this._collection = collection;
10 | }
11 |
12 | public next() {
13 | let iteration = this._iteration++;
14 | let collection = this._collection;
15 | let done = collection.length <= iteration;
16 |
17 | let value = done ? undefined : collection[iteration];
18 |
19 | return {
20 | done,
21 | value
22 | };
23 | }
24 | }
25 |
26 | QUnit.test('deferIterable', function(assert) {
27 | let bb = new Backburner(['zomg']);
28 | let order = 0;
29 |
30 | let tasks = {
31 | one: { count: 0, order: -1 },
32 | two: { count: 0, order: -1 },
33 | three: { count: 0, order: -1 }
34 | };
35 |
36 | function task1() {
37 | tasks.one.count++;
38 | tasks.one.order = order++;
39 | }
40 |
41 | function task2() {
42 | tasks.two.count++;
43 | tasks.two.order = order++;
44 | }
45 |
46 | function task3() {
47 | tasks.three.count++;
48 | tasks.three.order = order++;
49 | }
50 |
51 | let iterator = () => new Iterator([
52 | task1,
53 | task2,
54 | task3
55 | ]);
56 |
57 | bb.run(() => {
58 | bb.scheduleIterable('zomg', iterator);
59 |
60 | assert.deepEqual(tasks, {
61 | one: { count: 0, order: -1 },
62 | two: { count: 0, order: -1 },
63 | three: { count: 0, order: -1 }
64 | });
65 | });
66 |
67 | assert.deepEqual(tasks, {
68 | one: { count: 1, order: 0 },
69 | two: { count: 1, order: 1 },
70 | three: { count: 1, order: 2 }
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/tests/defer-debug-info-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 | import MockStableError, {
3 | overrideError,
4 | pushStackTrace,
5 | resetError,
6 | } from './utils/mock-stable-error';
7 |
8 | QUnit.module('tests/defer-debug-info', {
9 | beforeEach: function() {
10 | // @ts-ignore
11 | overrideError(MockStableError);
12 | },
13 |
14 | afterEach: function() {
15 | resetError();
16 | }
17 | });
18 |
19 | QUnit.test('_getDebugInfo returns empty object with DEBUG = false', function(assert) {
20 | assert.expect(1);
21 |
22 | let debugInfo;
23 | let bb = new Backburner(['render', 'afterRender']);
24 |
25 | bb.run(() => {
26 | debugInfo = bb.currentInstance && bb.currentInstance._getDebugInfo(bb.DEBUG);
27 |
28 | assert.equal(debugInfo, undefined);
29 | });
30 | });
31 |
32 | QUnit.test('_getDebugInfo returns debugInfo when DEBUG = true', function(assert) {
33 | assert.expect(1);
34 |
35 | let debugInfo;
36 | let method = () => {};
37 | let afterRenderStack = pushStackTrace('afterRender stack');
38 | let renderStack = pushStackTrace('render stack');
39 | let bb = new Backburner(['render', 'afterRender']);
40 |
41 | bb.DEBUG = true;
42 |
43 | bb.run(() => {
44 | bb.schedule('render', method);
45 | bb.schedule('afterRender', method);
46 |
47 | debugInfo = bb.currentInstance && bb.currentInstance._getDebugInfo(bb.DEBUG);
48 |
49 | assert.deepEqual(debugInfo, {
50 | render: [
51 | {
52 | target: null,
53 | method,
54 | args: undefined,
55 | stack: renderStack
56 | }
57 | ],
58 | afterRender: [
59 | {
60 | target: null,
61 | method,
62 | args: undefined,
63 | stack: afterRenderStack
64 | }
65 | ]
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/lib/backburner/platform.ts:
--------------------------------------------------------------------------------
1 | export interface IPlatform {
2 | setTimeout(fn: Function, ms: number): any;
3 | clearTimeout(id: any): void;
4 | next(): any;
5 | clearNext(): void;
6 | now(): number;
7 | }
8 |
9 | const SET_TIMEOUT = setTimeout;
10 | const NOOP = () => {};
11 |
12 | export function buildNext(flush: () => void): () => void {
13 | // Using "promises first" here to:
14 | //
15 | // 1) Ensure more consistent experience on browsers that
16 | // have differently queued microtasks (separate queues for
17 | // MutationObserver vs Promises).
18 | // 2) Ensure better debugging experiences (it shows up in Chrome
19 | // call stack as "Promise.then (async)") which is more consistent
20 | // with user expectations
21 | //
22 | // When Promise is unavailable use MutationObserver (mostly so that we
23 | // still get microtasks on IE11), and when neither MutationObserver and
24 | // Promise are present use a plain old setTimeout.
25 | if (typeof Promise === 'function') {
26 | const autorunPromise = Promise.resolve();
27 |
28 | return () => autorunPromise.then(flush);
29 | } else if (typeof MutationObserver === 'function') {
30 | let iterations = 0;
31 | let observer = new MutationObserver(flush);
32 | let node = document.createTextNode('');
33 | observer.observe(node, { characterData: true });
34 |
35 | return () => {
36 | iterations = ++iterations % 2;
37 | node.data = '' + iterations;
38 | return iterations;
39 | };
40 | } else {
41 | return () => SET_TIMEOUT(flush, 0);
42 | }
43 | }
44 |
45 | export function buildPlatform(flush: () => void): IPlatform {
46 | let clearNext = NOOP;
47 |
48 | return {
49 | setTimeout(fn, ms) {
50 | return setTimeout(fn, ms);
51 | },
52 |
53 | clearTimeout(timerId: number) {
54 | return clearTimeout(timerId);
55 | },
56 |
57 | now() {
58 | return Date.now();
59 | },
60 |
61 | next: buildNext(flush),
62 | clearNext,
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/lib/backburner/utils.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 |
3 | type MaybeError = Error | undefined;
4 |
5 | const NUMBER = /\d+/;
6 |
7 | const enum QueueItemPosition {
8 | target,
9 | method,
10 | args,
11 | stack
12 | }
13 |
14 | export const QUEUE_ITEM_LENGTH = 5;
15 | export const TIMERS_OFFSET = 7;
16 |
17 | export function isCoercableNumber(suspect) {
18 | let type = typeof suspect;
19 | return type === 'number' && suspect === suspect || type === 'string' && NUMBER.test(suspect);
20 | }
21 |
22 | export function getOnError(options) {
23 | return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]);
24 | }
25 |
26 | export function findItem(target, method, collection) {
27 | let index = -1;
28 |
29 | for (let i = 0, l = collection.length; i < l; i += QUEUE_ITEM_LENGTH) {
30 | if (collection[i] === target && collection[i + 1] === method) {
31 | index = i;
32 | break;
33 | }
34 | }
35 |
36 | return index;
37 | }
38 |
39 | export function findTimerItem(target, method, collection) {
40 | let index = -1;
41 |
42 | for (let i = 2, l = collection.length; i < l; i += TIMERS_OFFSET) {
43 | if (collection[i] === target && collection[i + 1] === method) {
44 | index = i - 2;
45 | break;
46 | }
47 | }
48 |
49 | return index;
50 | }
51 |
52 | export function getQueueItems(items: any[], queueItemLength: number, queueItemPositionOffset: number = 0): IQueueItem[] {
53 | let queueItems: IQueueItem[] = [];
54 |
55 | for (let i = 0; i < items.length; i += queueItemLength) {
56 | let maybeError: MaybeError = items[i + QueueItemPosition.stack + queueItemPositionOffset];
57 | let queueItem = {
58 | target: items[i + QueueItemPosition.target + queueItemPositionOffset],
59 | method: items[i + QueueItemPosition.method + queueItemPositionOffset],
60 | args: items[i + QueueItemPosition.args + queueItemPositionOffset],
61 | stack: maybeError !== undefined && 'stack' in maybeError ? maybeError.stack : ''
62 | };
63 |
64 | queueItems.push(queueItem);
65 | }
66 |
67 | return queueItems;
68 | }
69 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | Releases are mostly automated using
4 | [release-it](https://github.com/release-it/release-it/) and
5 | [lerna-changelog](https://github.com/lerna/lerna-changelog/).
6 |
7 | ## Preparation
8 |
9 | Since the majority of the actual release process is automated, the primary
10 | remaining task prior to releasing is confirming that all pull requests that
11 | have been merged since the last release have been labeled with the appropriate
12 | `lerna-changelog` labels and the titles have been updated to ensure they
13 | represent something that would make sense to our users. Some great information
14 | on why this is important can be found at
15 | [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall
16 | guiding principle here is that changelogs are for humans, not machines.
17 |
18 | When reviewing merged PR's the labels to be used are:
19 |
20 | * breaking - Used when the PR is considered a breaking change.
21 | * enhancement - Used when the PR adds a new feature or enhancement.
22 | * bug - Used when the PR fixes a bug included in a previous release.
23 | * documentation - Used when the PR adds or updates documentation.
24 | * internal - Used for internal changes that still require a mention in the
25 | changelog/release notes.
26 |
27 | ## Release
28 |
29 | Once the prep work is completed, the actual release is straight forward:
30 |
31 | * First, ensure that you have installed your projects dependencies:
32 |
33 | ```sh
34 | yarn install
35 | ```
36 |
37 | * Second, ensure that you have obtained a
38 | [GitHub personal access token][generate-token] with the `repo` scope (no
39 | other permissions are needed). Make sure the token is available as the
40 | `GITHUB_AUTH` environment variable.
41 |
42 | For instance:
43 |
44 | ```bash
45 | export GITHUB_AUTH=abc123def456
46 | ```
47 |
48 | [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable
49 |
50 | * And last (but not least 😁) do your release.
51 |
52 | ```sh
53 | npx release-it
54 | ```
55 |
56 | [release-it](https://github.com/release-it/release-it/) manages the actual
57 | release process. It will prompt you to to choose the version number after which
58 | you will have the chance to hand tweak the changelog to be used (for the
59 | `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging,
60 | pushing the tag and commits, etc.
61 |
--------------------------------------------------------------------------------
/tests/debug-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/debug');
4 |
5 | QUnit.test('schedule - DEBUG flag enables stack tagging', function(assert) {
6 | let bb = new Backburner(['one']);
7 |
8 | bb.schedule('one', () => {});
9 | if (!bb.currentInstance) {
10 | throw new Error('bb has no instance');
11 | }
12 |
13 | assert.ok(bb.currentInstance && !bb.currentInstance.queues.one.stackFor(0), 'No stack is recorded');
14 |
15 | bb.DEBUG = true;
16 |
17 | bb.schedule('one', () => {});
18 |
19 | if (new Error().stack) { // workaround for CLI runner :(
20 | assert.expect(4);
21 | let stack = bb.currentInstance && bb.currentInstance.queues.one.stackFor(1);
22 | assert.ok(typeof stack === 'string', 'A stack is recorded');
23 |
24 | let onError = function(error, errorRecordedForStack) {
25 | assert.ok(errorRecordedForStack, 'errorRecordedForStack passed to error function');
26 | assert.ok(errorRecordedForStack.stack, 'stack is recorded');
27 | };
28 |
29 | bb = new Backburner(['errors'], { onError });
30 | bb.DEBUG = true;
31 |
32 | bb.run(() => {
33 | bb.schedule('errors', () => {
34 | throw new Error('message!');
35 | });
36 | });
37 | }
38 | });
39 |
40 | QUnit.test('later - DEBUG flag off does not capture stack', function(assert) {
41 | let done = assert.async();
42 | let onError = function(error, errorRecordedForStack) {
43 | assert.strictEqual(errorRecordedForStack, undefined, 'errorRecordedForStack is not passed to error function when DEBUG is not set');
44 | done();
45 | };
46 | let bb = new Backburner(['one'], { onError });
47 |
48 | bb.later(() => {
49 | throw new Error('message!');
50 | });
51 | });
52 |
53 | if (new Error().stack) { // workaround for CLI runner :(
54 | QUnit.test('later - DEBUG flag on captures stack', function(assert) {
55 | assert.expect(3);
56 |
57 | let done = assert.async();
58 | let onError = function(error, errorRecordedForStack) {
59 | assert.ok(errorRecordedForStack, 'errorRecordedForStack passed to error function');
60 | assert.ok(errorRecordedForStack.stack, 'stack is recorded');
61 | assert.ok(errorRecordedForStack.stack.indexOf('later') > -1, 'stack includes `later` invocation');
62 | done();
63 | };
64 | let bb = new Backburner(['one'], { onError });
65 | bb.DEBUG = true;
66 |
67 | bb.later(() => {
68 | throw new Error('message!');
69 | });
70 | });
71 | }
72 |
--------------------------------------------------------------------------------
/tests/async-stack-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | const skipIfNotSupported = !!console['createTask'] ? QUnit.test : QUnit.skip;
4 |
5 | QUnit.module('tests/async_stacks');
6 |
7 | QUnit.test('schedule - does not affect normal behaviour', function(assert) {
8 | let bb = new Backburner(['one']);
9 | let callCount = 0;
10 |
11 | bb.run(() => {
12 | bb.schedule('one', () => callCount += 1)
13 | bb.schedule('one', () => callCount += 1)
14 | });
15 | assert.strictEqual(callCount, 2, 'schedule works correctly with ASYNC_STACKS disabled');
16 |
17 | bb.ASYNC_STACKS = true;
18 |
19 | bb.run(() => {
20 | bb.schedule('one', () => callCount += 1)
21 | bb.schedule('one', () => callCount += 1)
22 | });
23 | assert.strictEqual(callCount, 4, 'schedule works correctly with ASYNC_STACKS enabled');
24 | });
25 |
26 | skipIfNotSupported('schedule - ASYNC_STACKS flag enables async stack tagging', function(assert) {
27 | let bb = new Backburner(['one']);
28 |
29 | bb.schedule('one', () => {});
30 |
31 | assert.true(bb.currentInstance && (bb.currentInstance.queues.one.consoleTaskFor(0) === undefined), 'No consoleTask is stored');
32 |
33 | bb.ASYNC_STACKS = true;
34 |
35 | bb.schedule('one', () => {});
36 |
37 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(1);
38 | assert.true(!!task?.run, 'consoleTask is stored in queue');
39 | });
40 |
41 | QUnit.test('later - ASYNC_STACKS does not affect normal behaviour', function(assert) {
42 | let bb = new Backburner(['one']);
43 | let done = assert.async();
44 | bb.ASYNC_STACKS = true;
45 |
46 | bb.later(() => {
47 | assert.true(true, 'timer called')
48 | done()
49 | });
50 | });
51 |
52 |
53 | skipIfNotSupported('later - skips async stack when ASYNC_STACKS is false', function(assert) {
54 | let done = assert.async();
55 | let bb = new Backburner(['one']);
56 |
57 | bb.later(() => {
58 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(0, true);
59 | assert.true(bb.currentInstance && (bb.currentInstance.queues.one.consoleTaskFor(0, true) === undefined), 'consoleTask is not stored')
60 | done();
61 | });
62 | });
63 |
64 |
65 | skipIfNotSupported('later - ASYNC_STACKS flag enables async stack tagging', function(assert) {
66 | let done = assert.async();
67 | let bb = new Backburner(['one']);
68 | bb.ASYNC_STACKS = true;
69 |
70 | bb.later(() => {
71 | const task = bb.currentInstance && bb.currentInstance.queues.one.consoleTaskFor(0, true);
72 | assert.true(!!task?.run, 'consoleTask is stored in timer queue and then passed to runloop queue')
73 | done();
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/bench/benches/schedule-flush.js:
--------------------------------------------------------------------------------
1 | function prodSetup() {
2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
3 |
4 | var target = {
5 | someMethod: function() { }
6 | };
7 | }
8 |
9 | function asyncStackSetup() {
10 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
11 | backburner.ASYNC_STACKS = true;
12 |
13 | var target = {
14 | someMethod: function() { }
15 | };
16 | }
17 |
18 | function debugSetup() {
19 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
20 | backburner.DEBUG = true;
21 |
22 | var target = {
23 | someMethod: function() { }
24 | };
25 | }
26 |
27 | let scenarios = [];
28 |
29 | let base = [{
30 | name: 'Schedule & Flush - function',
31 |
32 | Backburner: Backburner,
33 |
34 | fn: function() {
35 | backburner.begin();
36 | backburner.schedule('render', function() {});
37 | backburner.end();
38 | }
39 | }, {
40 | name: 'Schedule & Flush - target, function',
41 |
42 | Backburner: Backburner,
43 |
44 | fn: function() {
45 | backburner.begin();
46 | backburner.schedule('render', target, function() {});
47 | backburner.end();
48 | }
49 | }, {
50 | name: 'Schedule & Flush - target, string method name',
51 |
52 | Backburner: Backburner,
53 |
54 | fn: function() {
55 | backburner.begin();
56 | backburner.schedule('render', target, 'someMethod');
57 | backburner.end();
58 | }
59 | }, {
60 | name: 'Schedule & Flush - target, string method name, 1 argument',
61 |
62 | Backburner: Backburner,
63 |
64 | fn: function() {
65 | backburner.begin();
66 | backburner.schedule('render', target, 'someMethod', 'foo');
67 | backburner.end();
68 | }
69 | }, {
70 | name: 'Schedule & Flush - target, string method name, 2 arguments',
71 |
72 | Backburner: Backburner,
73 |
74 | fn: function() {
75 | backburner.begin();
76 | backburner.schedule('render', target, 'someMethod', 'foo', 'bar');
77 | backburner.end();
78 | }
79 | }];
80 |
81 | base.forEach(item => {
82 | let prodItem = Object.assign({}, item);
83 | prodItem.setup = prodSetup;
84 | scenarios.push(prodItem);
85 | });
86 |
87 | base.forEach(item => {
88 | let debugItem = Object.assign({}, item);
89 | debugItem.name = `ASYNC_STACKS - ${debugItem.name}`;
90 | debugItem.setup = asyncStackSetup;
91 | scenarios.push(debugItem);
92 | });
93 |
94 | base.forEach(item => {
95 | let debugItem = Object.assign({}, item);
96 | debugItem.name = `DEBUG - ${debugItem.name}`;
97 | debugItem.setup = debugSetup;
98 | scenarios.push(debugItem);
99 | });
100 |
101 | module.exports = scenarios;
102 |
--------------------------------------------------------------------------------
/tests/queue-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/queue');
4 |
5 | QUnit.test('actions scheduled on previous queue, start over from beginning', function(assert) {
6 | assert.expect(5);
7 |
8 | let bb = new Backburner(['one', 'two']);
9 | let step = 0;
10 |
11 | bb.run(function() {
12 | assert.equal(step++, 0, '0');
13 |
14 | bb.schedule('two', null, function() {
15 | assert.equal(step++, 1, '1');
16 |
17 | bb.schedule('one', null, function() {
18 | assert.equal(step++, 3, '3');
19 | });
20 | });
21 |
22 | bb.schedule('two', null, function() {
23 | assert.equal(step++, 2, '2');
24 | });
25 | });
26 |
27 | assert.equal(step, 4, '4');
28 | });
29 |
30 | QUnit.test('Queue#flush should be recursive if new items are added', function(assert) {
31 | assert.expect(2);
32 |
33 | let bb = new Backburner(['one']);
34 | let count = 0;
35 |
36 | bb.run(function() {
37 | function increment() {
38 | if (++count < 3) {
39 | bb.schedule('one', increment);
40 | }
41 |
42 | if (count === 3) {
43 |
44 | bb.schedule('one', increment);
45 | }
46 | }
47 |
48 | increment();
49 | assert.equal(count, 1, 'should not have run yet');
50 |
51 | let currentInstance = bb.currentInstance;
52 | if (currentInstance) {
53 | currentInstance.queues.one.flush();
54 | }
55 | assert.equal(count, 4, 'should have run all scheduled methods, even ones added during flush');
56 | });
57 |
58 | });
59 |
60 | QUnit.test('Default queue is automatically set to first queue if none is provided', function(assert) {
61 | let bb = new Backburner(['one', 'two']);
62 | assert.equal(bb.defaultQueue, 'one');
63 | });
64 |
65 | QUnit.test('Default queue can be manually configured', function(assert) {
66 | let bb = new Backburner(['one', 'two'], {
67 | defaultQueue: 'two'
68 | });
69 |
70 | assert.equal(bb.defaultQueue, 'two');
71 | });
72 |
73 | QUnit.test('onBegin and onEnd are called and passed the correct parameters', function(assert) {
74 | assert.expect(2);
75 |
76 | let befores: (any | null | undefined)[] = [];
77 | let afters: (any | null | undefined)[] = [];
78 | let expectedBefores: (any | null | undefined)[] = [];
79 | let expectedAfters: (any | null | undefined)[] = [];
80 | let outer: any;
81 | let inner: any;
82 |
83 | let bb = new Backburner(['one'], {
84 | onBegin: function(current, previous) {
85 | befores.push(current);
86 | befores.push(previous);
87 | },
88 | onEnd: function(current, next) {
89 | afters.push(current);
90 | afters.push(next);
91 | }
92 | });
93 |
94 | bb.run(function() {
95 | outer = bb.currentInstance;
96 | bb.run(function() {
97 | inner = bb.currentInstance;
98 | });
99 | });
100 |
101 | expectedBefores = [outer, null, inner, outer];
102 | expectedAfters = [inner, outer, outer, null];
103 |
104 | assert.deepEqual(befores, expectedBefores, 'before callbacks successful');
105 | assert.deepEqual(afters, expectedAfters, 'after callback successful');
106 | });
107 |
--------------------------------------------------------------------------------
/bench/benches/schedule-cancel.js:
--------------------------------------------------------------------------------
1 | function sharedSetup() {
2 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
3 |
4 | var target = {
5 | someMethod: function() { }
6 | };
7 | }
8 |
9 | module.exports = [{
10 | name: 'Schedule & Cancel - function',
11 |
12 | Backburner: Backburner,
13 |
14 | setup: sharedSetup,
15 |
16 | fn: function() {
17 | var timer = backburner.schedule('render', function() {
18 | // no body
19 | });
20 | backburner.cancel(timer);
21 | }
22 | }, {
23 | name: 'Schedule & Cancel - target, function',
24 |
25 | Backburner: Backburner,
26 |
27 | setup: sharedSetup,
28 |
29 | fn: function() {
30 | var timer = backburner.schedule('render', target, function() {
31 | // no body
32 | });
33 | backburner.cancel(timer);
34 | }
35 | }, {
36 | name: 'Schedule & Cancel - target, string method name',
37 |
38 | Backburner: Backburner,
39 |
40 | setup: sharedSetup,
41 |
42 | fn: function() {
43 | var timer = backburner.schedule('render', target, 'someMethod');
44 | backburner.cancel(timer);
45 | }
46 | }, {
47 | name: 'Schedule & Cancel - target, string method name, 1 argument',
48 |
49 | Backburner: Backburner,
50 |
51 | setup: sharedSetup,
52 |
53 | fn: function() {
54 | var timer = backburner.schedule('render', target, 'someMethod', 'foo');
55 | backburner.cancel(timer);
56 | }
57 | }, {
58 | name: 'Schedule & Cancel - target, string method name, 2 arguments',
59 |
60 | Backburner: Backburner,
61 |
62 | setup: sharedSetup,
63 |
64 | fn: function() {
65 | var timer = backburner.schedule('render', target, 'someMethod', 'foo', 'bar');
66 | backburner.cancel(timer);
67 | }
68 | }, {
69 | name: 'Schedule & Cancel - prescheduled, same queue - target, string method name',
70 |
71 | Backburner: Backburner,
72 |
73 | setup: function() {
74 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
75 |
76 | var target = {
77 | someMethod: function() { }
78 | };
79 |
80 | var prescheduleSetupIterations = 100;
81 | while (prescheduleSetupIterations--) {
82 | backburner.schedule('render', function() { });
83 | }
84 | },
85 |
86 | fn: function() {
87 | var timer = backburner.schedule('render', target, 'someMethod');
88 | backburner.cancel(timer);
89 | }
90 | }, {
91 | name: 'Schedule & Cancel - prescheduled, separate queue - target, string method name',
92 |
93 | Backburner: Backburner,
94 |
95 | setup: function() {
96 | var backburner = new this.Backburner(["sync", "actions", "routerTransitions", "render", "afterRender", "destroy", "rsvpAfter"]);
97 |
98 | var target = {
99 | someMethod: function() { }
100 | };
101 |
102 | var prescheduleSetupIterations = 100;
103 | while (prescheduleSetupIterations--) {
104 | backburner.schedule('actions', function() { });
105 | }
106 | },
107 |
108 | fn: function() {
109 | var timer = backburner.schedule('render', target, 'someMethod');
110 | backburner.cancel(timer);
111 | }
112 | }];
113 |
--------------------------------------------------------------------------------
/tests/queue-debug-info-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 | import MockStableError, {
3 | overrideError,
4 | pushStackTrace,
5 | resetError,
6 | } from './utils/mock-stable-error';
7 |
8 | QUnit.module('tests/queue-debug-info', {
9 | beforeEach: function() {
10 | // @ts-ignore
11 | overrideError(MockStableError);
12 | },
13 |
14 | afterEach: function() {
15 | resetError();
16 | }
17 | });
18 |
19 | QUnit.test('getDebugInfo returns empty array when no debug info is present.', function(assert) {
20 | assert.expect(1);
21 |
22 | let debugInfo;
23 | let bb = new Backburner(['one']);
24 |
25 | bb.run(function() {
26 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG);
27 |
28 | assert.equal(debugInfo, undefined, 'DebugInfo is undefined when DEBUG = false');
29 | });
30 | });
31 |
32 | QUnit.test('getDebugInfo returns minimal debug info when one item in queue.', function(assert) {
33 | assert.expect(2);
34 |
35 | let debugInfo;
36 | let method = function() {
37 | assert.ok(true);
38 | };
39 | let stack = pushStackTrace('Top of stack');
40 | let bb = new Backburner(['one']);
41 |
42 | bb.DEBUG = true;
43 |
44 | bb.run(function() {
45 | bb.schedule('one', method);
46 |
47 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG);
48 |
49 | assert.deepEqual(debugInfo, [{
50 | target: null,
51 | method,
52 | args: undefined,
53 | stack
54 | }]);
55 | });
56 | });
57 |
58 | QUnit.test('getDebugInfo returns full debug info when one item in queue.', function(assert) {
59 | assert.expect(2);
60 |
61 | let debugInfo;
62 | let target = {};
63 | let method = function() {
64 | assert.ok(true);
65 | };
66 | let arg1 = 1;
67 | let arg2 = 2;
68 | let stack = pushStackTrace('Top of stack');
69 | let bb = new Backburner(['one']);
70 |
71 | bb.DEBUG = true;
72 |
73 | bb.run(function() {
74 | bb.schedule('one', target, method, arg1, arg2);
75 |
76 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG);
77 |
78 | assert.deepEqual(debugInfo, [{
79 | target,
80 | method,
81 | args: [arg1, arg2],
82 | stack
83 | }]);
84 | });
85 | });
86 |
87 | QUnit.test('getDebugInfo returns debug info when multiple items in queue.', function(assert) {
88 | assert.expect(3);
89 |
90 | let debugInfo;
91 | let method = function() {
92 | assert.ok(true);
93 | };
94 | let bottom = pushStackTrace('Bottom');
95 | let top = pushStackTrace('Top');
96 | let bb = new Backburner(['one']);
97 |
98 | bb.DEBUG = true;
99 |
100 | bb.run(function() {
101 | bb.schedule('one', method);
102 | bb.schedule('one', method);
103 |
104 | debugInfo = bb.currentInstance && bb.currentInstance.queues.one._getDebugInfo(bb.DEBUG);
105 |
106 | assert.deepEqual(debugInfo, [
107 | {
108 | target: null,
109 | method,
110 | args: undefined,
111 | stack: top
112 | },
113 | {
114 | target: null,
115 | method,
116 | args: undefined,
117 | stack: bottom
118 | }
119 | ]);
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/lib/backburner/deferred-action-queues.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 | import Queue, { QUEUE_STATE } from './queue';
3 |
4 | export interface IDebugInfo {
5 | [key: string]: IQueueItem[] | undefined;
6 | }
7 |
8 | export default class DeferredActionQueues {
9 | public queues: { [name: string]: Queue } = {};
10 | public queueNameIndex = 0;
11 |
12 | private queueNames: string[];
13 |
14 | constructor(queueNames: string[] = [], options: any) {
15 | this.queueNames = queueNames;
16 |
17 | queueNames.reduce(function(queues, queueName) {
18 | queues[queueName] = new Queue(queueName, options[queueName], options);
19 | return queues;
20 | }, this.queues);
21 | }
22 |
23 | /**
24 | * @method schedule
25 | * @param {String} queueName
26 | * @param {Any} target
27 | * @param {Any} method
28 | * @param {Any} args
29 | * @param {Boolean} onceFlag
30 | * @param {Any} stack
31 | * @return queue
32 | */
33 | public schedule(queueName: string, target: any, method: any, args: any, onceFlag: boolean, stack: any, consoleTask: any) {
34 | let queues = this.queues;
35 | let queue = queues[queueName];
36 |
37 | if (queue === undefined) {
38 | throw new Error(`You attempted to schedule an action in a queue (${queueName}) that doesn\'t exist`);
39 | }
40 |
41 | if (method === undefined || method === null) {
42 | throw new Error(`You attempted to schedule an action in a queue (${queueName}) for a method that doesn\'t exist`);
43 | }
44 |
45 | this.queueNameIndex = 0;
46 |
47 | if (onceFlag) {
48 | return queue.pushUnique(target, method, args, stack, consoleTask);
49 | } else {
50 | return queue.push(target, method, args, stack, consoleTask);
51 | }
52 | }
53 |
54 | /**
55 | * DeferredActionQueues.flush() calls Queue.flush()
56 | *
57 | * @method flush
58 | * @param {Boolean} fromAutorun
59 | */
60 | public flush(fromAutorun = false) {
61 | let queue;
62 | let queueName;
63 | let numberOfQueues = this.queueNames.length;
64 |
65 | while (this.queueNameIndex < numberOfQueues) {
66 | queueName = this.queueNames[this.queueNameIndex];
67 | queue = this.queues[queueName];
68 |
69 | if (queue.hasWork() === false) {
70 | this.queueNameIndex++;
71 | if (fromAutorun && this.queueNameIndex < numberOfQueues) {
72 | return QUEUE_STATE.Pause;
73 | }
74 | } else {
75 | if (queue.flush(false /* async */) === QUEUE_STATE.Pause) {
76 | return QUEUE_STATE.Pause;
77 | }
78 | }
79 | }
80 | }
81 |
82 | /**
83 | * Returns debug information for the current queues.
84 | *
85 | * @method _getDebugInfo
86 | * @param {Boolean} debugEnabled
87 | * @returns {IDebugInfo | undefined}
88 | */
89 | public _getDebugInfo(debugEnabled: boolean): IDebugInfo | undefined {
90 | if (debugEnabled) {
91 | let debugInfo: IDebugInfo = {};
92 | let queue: Queue;
93 | let queueName: string;
94 | let numberOfQueues: number = this.queueNames.length;
95 | let i: number = 0;
96 |
97 | while (i < numberOfQueues) {
98 | queueName = this.queueNames[i];
99 | queue = this.queues[queueName];
100 |
101 | debugInfo[queueName] = queue._getDebugInfo(debugEnabled);
102 | i++;
103 | }
104 |
105 | return debugInfo;
106 | }
107 |
108 | return;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/tests/multi-turn-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner, { buildPlatform } from 'backburner';
2 |
3 | QUnit.module('tests/multi-turn');
4 |
5 | const queue: any[] = [];
6 | let platform;
7 | function buildFakePlatform(flush) {
8 | platform = buildPlatform(flush);
9 | platform.flushSync = function() {
10 | flush();
11 | };
12 | return platform;
13 | }
14 |
15 | QUnit.test('basic', function(assert) {
16 | let bb = new Backburner(['zomg'], {
17 | // This is just a place holder for now, but somehow the system needs to
18 | // know to when to stop
19 | mustYield() {
20 | return true; // yield after each step, for now.
21 | },
22 | _buildPlatform: buildFakePlatform
23 | });
24 |
25 | let order = -1;
26 | let tasks = {
27 | one: { count: 0, order: -1 },
28 | two: { count: 0, order: -1 },
29 | three: { count: 0, order: -1 }
30 | };
31 |
32 | bb.schedule('zomg', null, () => {
33 | tasks.one.count++;
34 | tasks.one.order = ++order;
35 | });
36 |
37 | bb.schedule('zomg', null, () => {
38 | tasks.two.count++;
39 | tasks.two.order = ++order;
40 | });
41 |
42 | bb.schedule('zomg', null, () => {
43 | tasks.three.count++;
44 | tasks.three.order = ++order;
45 | });
46 |
47 | assert.deepEqual(tasks, {
48 | one: { count: 0, order: -1 },
49 | two: { count: 0, order: -1 },
50 | three: { count: 0, order: -1 }
51 | }, 'no tasks have been run before the platform flushes');
52 |
53 | platform.flushSync();
54 |
55 | assert.deepEqual(tasks, {
56 | one: { count: 1, order: 0 },
57 | two: { count: 0, order: -1 },
58 | three: { count: 0, order: -1 }
59 | }, 'TaskOne has been run before the platform flushes');
60 |
61 | platform.flushSync();
62 |
63 | assert.deepEqual(tasks, {
64 | one: { count: 1, order: 0 },
65 | two: { count: 1, order: 1 },
66 | three: { count: 0, order: -1 }
67 | }, 'TaskOne and TaskTwo has been run before the platform flushes');
68 |
69 | platform.flushSync();
70 |
71 | assert.deepEqual(tasks, {
72 | one: { count: 1, order: 0 },
73 | two: { count: 1, order: 1 },
74 | three: { count: 1, order: 2 }
75 | }, 'TaskOne, TaskTwo and TaskThree has been run before the platform flushes');
76 | });
77 |
78 | QUnit.test('properly cancel items which are added during flush', function(assert) {
79 | let bb = new Backburner(['zomg'], {
80 | // This is just a place holder for now, but somehow the system needs to
81 | // know to when to stop
82 | mustYield() {
83 | return true; // yield after each step, for now.
84 | },
85 |
86 | _buildPlatform: buildFakePlatform
87 | });
88 |
89 | let fooCalled = 0;
90 | let barCalled = 0;
91 |
92 | let obj1 = {
93 | foo() {
94 | fooCalled++;
95 | }
96 | };
97 |
98 | let obj2 = {
99 | bar() {
100 | barCalled++;
101 | }
102 | };
103 |
104 | bb.scheduleOnce('zomg', obj1, 'foo');
105 | bb.scheduleOnce('zomg', obj1, 'foo');
106 | bb.scheduleOnce('zomg', obj2, 'bar');
107 | bb.scheduleOnce('zomg', obj2, 'bar');
108 |
109 | platform.flushSync();
110 |
111 | let timer1 = bb.scheduleOnce('zomg', obj1, 'foo');
112 | let timer2 = bb.scheduleOnce('zomg', obj2, 'bar');
113 | bb.cancel(timer1);
114 | bb.cancel(timer2);
115 |
116 | platform.flushSync();
117 | platform.flushSync();
118 | platform.flushSync();
119 |
120 | assert.equal(fooCalled, 1, 'fooCalled');
121 | assert.equal(barCalled, 1, 'barCalled');
122 |
123 | });
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # backburner.js [](https://travis-ci.org/BackburnerJS/backburner.js)
2 | A rewrite of the Ember.js run loop as a generic microlibrary.
3 |
4 | ## TL;DR
5 | A priority queue that will efficiently batch, order, reorder and process work; done via scheduling work on specific queues.
6 |
7 | ## API
8 |
9 | ### Constructor
10 |
11 | | Constructor | Description |
12 | |---|---|
13 | | `new Backburner()` | instantiate a Backburner instance with an array of queue names |
14 |
15 | ### Instance methods
16 |
17 | | Method | Description |
18 | |---|---|
19 | | `Backburner#run` | execute the passed function and flush any deferred actions |
20 | | `Backburner#defer` | defer the passed function to run inside the specified queue |
21 | | `Backburner#deferOnce` | defer the passed function to run inside the specified queue, only execute it once |
22 | | `Backburner#setTimeout` | execute the passed function in a specified amount of time |
23 | | `Backburner#debounce` | execute the passed function in a specified amount of time, reset timer upon additional calls |
24 | | `Backburner#throttle` | rate-limit the passed function for a specified amount of time |
25 | | `Backburner#cancel` | cancel a `deferOnce`, `setTimeout`, `debounce` or `throttle` |
26 | | `Backburner#on` | Add an event callback. Supports the following events:
- `begin` - Fires whenever the runloop begins. Callbacks are passed the current instance and the previous instance.
- `end` - Fires whenever the runloop ends. Callbacks are passed the current instance and the next instance.
|
27 | | `Backburner#off` | Removes an event callback |
28 | | `Backburner#join` | Join the passed method with an existing queue and execute immediately, if there isn't one use `Backburner#run` |
29 | | `Backburner#getDebugInfo` | Returns debug information for counters, timers and queues, which includes surfacing the stack trace information for the respective calls |
30 |
31 | #### Alias
32 |
33 | | Alias | Description |
34 | |---|---|
35 | | `Backburner#schedule` | same as `defer` |
36 | | `Backburner#scheduleOnce` | same as `deferOnce` |
37 | | `Backburner#later` | same as `setTimeout` |
38 |
39 | ## Example usage
40 |
41 | The following code will only cause a single DOM manipulation:
42 |
43 | ```html
44 |
45 |
46 |
47 |
48 | Backburner demo
49 |
50 |
51 |
52 |
53 |
54 |
75 |
76 |
77 | ```
78 |
79 | ## Benchmarks
80 |
81 | The `/bench` directory includes a number of performance benchmarks.
82 |
83 | To run in `node`:
84 |
85 | ```bash
86 | yarn ember build
87 | node ./bench/index.js # (all benchmarks)
88 | node ./bench/index.js ./bench/benches/some-file.js # (specific benchmark)
89 | ```
90 |
91 | Or to run in a browser, run `yarn ember server`, and visit `http://localhost:4200` in a browser. Be aware that having the developer tools open/closed can affect JS performance.
92 |
--------------------------------------------------------------------------------
/tests/configurable-timeout-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner, { buildPlatform } from 'backburner';
2 |
3 | QUnit.module('tests/configurable-timeout');
4 |
5 | QUnit.test('We can configure a custom platform', function(assert) {
6 | assert.expect(1);
7 |
8 | let bb = new Backburner(['one'], {
9 | _buildPlatform(flush) {
10 | let platform = buildPlatform(flush);
11 | platform['isFakePlatform'] = true;
12 | return platform;
13 | }
14 | });
15 |
16 | assert.ok(bb['_platform']!['isFakePlatform'], 'We can pass in a custom platform');
17 | });
18 |
19 | QUnit.test('We can use a custom setTimeout', function(assert) {
20 | assert.expect(1);
21 | let done = assert.async();
22 |
23 | let customNextWasUsed = false;
24 | let bb = new Backburner(['one'], {
25 | _buildPlatform(flush) {
26 | return {
27 | next() {
28 | throw new TypeError('NOT IMPLEMENTED');
29 | },
30 | clearNext() { },
31 | setTimeout(cb) {
32 | customNextWasUsed = true;
33 | return setTimeout(cb);
34 | },
35 | clearTimeout(timer) {
36 | return clearTimeout(timer);
37 | },
38 | now() {
39 | return Date.now();
40 | },
41 | isFakePlatform: true
42 | };
43 | }
44 | });
45 |
46 | bb.setTimeout(() => {
47 | assert.ok(customNextWasUsed , 'custom later was used');
48 | done();
49 | });
50 | });
51 |
52 | QUnit.test('We can use a custom next', function(assert) {
53 | assert.expect(1);
54 | let done = assert.async();
55 |
56 | let customNextWasUsed = false;
57 | let bb = new Backburner(['one'], {
58 | _buildPlatform(flush) {
59 | return {
60 | setTimeout() {
61 | throw new TypeError('NOT IMPLEMENTED');
62 | },
63 | clearTimeout(timer) {
64 | return clearTimeout(timer);
65 | },
66 | next() {
67 | // next is used for the autorun
68 | customNextWasUsed = true;
69 | return setTimeout(flush);
70 | },
71 | clearNext() { },
72 | now() { return Date.now(); },
73 | isFakePlatform: true
74 | };
75 | }
76 | });
77 |
78 | bb.scheduleOnce('one', () => {
79 | assert.ok(customNextWasUsed , 'custom later was used');
80 | done();
81 | });
82 | });
83 |
84 | QUnit.test('We can use a custom clearTimeout', function(assert) {
85 | assert.expect(2);
86 |
87 | let functionWasCalled = false;
88 | let customClearTimeoutWasUsed = false;
89 | let bb = new Backburner(['one'], {
90 | _buildPlatform(flush) {
91 | return {
92 | setTimeout(method, wait) {
93 | return setTimeout(method, wait);
94 | },
95 | clearTimeout(timer) {
96 | customClearTimeoutWasUsed = true;
97 | return clearTimeout(timer);
98 | },
99 | next() {
100 | return setTimeout(flush, 0);
101 | },
102 | clearNext() {
103 | customClearTimeoutWasUsed = true;
104 | },
105 | now() {
106 | return Date.now();
107 | }
108 | };
109 | }
110 | });
111 |
112 | bb.scheduleOnce('one', () => functionWasCalled = true);
113 | bb.cancelTimers();
114 |
115 | bb.run(() => {
116 | bb.scheduleOnce('one', () => {
117 | assert.ok(!functionWasCalled, 'function was not called');
118 | assert.ok(customClearTimeoutWasUsed, 'custom clearTimeout was used');
119 | });
120 | });
121 | });
122 |
123 | QUnit.test('We can use a custom now', function(assert) {
124 | assert.expect(1);
125 | let done = assert.async();
126 |
127 | let currentTime = 10;
128 | let customNowWasUsed = false;
129 | let bb = new Backburner(['one'], {
130 | _buildPlatform(flush) {
131 | return {
132 | setTimeout(method, wait) {
133 | return setTimeout(method, wait);
134 | },
135 | clearTimeout(id) {
136 | clearTimeout(id);
137 | },
138 | next() {
139 | return setTimeout(flush, 0);
140 | },
141 | clearNext() { },
142 | now() {
143 | customNowWasUsed = true;
144 | return currentTime += 10;
145 | },
146 | };
147 | }
148 | });
149 |
150 | bb.later(() => {
151 | assert.ok(customNowWasUsed , 'custom now was used');
152 | done();
153 | }, 10);
154 | });
155 |
--------------------------------------------------------------------------------
/tests/events-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/events');
4 |
5 | QUnit.test('end event should fire after runloop completes', function(assert) {
6 | assert.expect(3);
7 | let callNumber = 0;
8 |
9 | let bb = new Backburner(['one', 'two']);
10 |
11 | bb.on('end', () => callNumber++);
12 |
13 | function funcOne() {
14 | assert.equal(callNumber, 0);
15 | }
16 |
17 | function funcTwo() {
18 | assert.equal(callNumber, 0);
19 | }
20 |
21 | bb.run(() => {
22 | bb.schedule('one', null, funcOne);
23 | bb.schedule('two', null, funcTwo);
24 | });
25 |
26 | assert.equal(callNumber, 1);
27 | });
28 |
29 | QUnit.test('end event should fire before onEnd', function(assert) {
30 | assert.expect(3);
31 | let callNumber = 0;
32 |
33 | let bb = new Backburner(['one', 'two'], {
34 | onEnd() {
35 | assert.equal(callNumber, 1);
36 | }
37 | });
38 |
39 | bb.on('end', () => callNumber++);
40 |
41 | function funcOne() {
42 | assert.equal(callNumber, 0);
43 | }
44 |
45 | function funcTwo() {
46 | assert.equal(callNumber, 0);
47 | }
48 |
49 | bb.run(() => {
50 | bb.schedule('one', null, funcOne);
51 | bb.schedule('two', null, funcTwo);
52 | });
53 | });
54 |
55 | QUnit.test('end event should be passed the current and next instance', function(assert) {
56 | assert.expect(4);
57 | let callNumber = 0;
58 |
59 | let firstArgument = null;
60 | let secondArgument = null;
61 |
62 | let bb = new Backburner(['one'], {
63 | onEnd(first, second) {
64 | assert.equal(firstArgument, first);
65 | assert.equal(secondArgument, second);
66 | }
67 | });
68 |
69 | bb.on('end', (first, second) => {
70 | firstArgument = first;
71 | secondArgument = second;
72 | });
73 |
74 | bb.run(() => bb.schedule('one', null, () => {}));
75 | bb.run(() => bb.schedule('one', null, () => {}));
76 | });
77 | // blah
78 |
79 | QUnit.test('begin event should fire before runloop begins', function(assert) {
80 | assert.expect(4);
81 | let callNumber = 0;
82 |
83 | let bb = new Backburner(['one', 'two']);
84 |
85 | bb.on('begin', () => callNumber++);
86 |
87 | function funcOne() {
88 | assert.equal(callNumber, 1);
89 | }
90 |
91 | function funcTwo() {
92 | assert.equal(callNumber, 1);
93 | }
94 |
95 | assert.equal(callNumber, 0);
96 | bb.run(() => {
97 | bb.schedule('one', null, funcOne);
98 | bb.schedule('two', null, funcTwo);
99 | });
100 |
101 | assert.equal(callNumber, 1);
102 | });
103 |
104 | QUnit.test('begin event should fire before onBegin', function(assert) {
105 | assert.expect(1);
106 | let callNumber = 0;
107 |
108 | let bb = new Backburner(['one', 'two'], {
109 | onBegin() {
110 | assert.equal(callNumber, 1);
111 | }
112 | });
113 |
114 | bb.on('begin', () => callNumber++);
115 |
116 | bb.run(() => {
117 | bb.schedule('one', null, () => {});
118 | bb.schedule('two', null, () => {});
119 | });
120 | });
121 |
122 | QUnit.test('begin event should be passed the current and previous instance', function(assert) {
123 | assert.expect(4);
124 | let callNumber = 0;
125 |
126 | let firstArgument = null;
127 | let secondArgument = null;
128 |
129 | let bb = new Backburner(['one'], {
130 | onBegin(first, second) {
131 | assert.equal(firstArgument, first);
132 | assert.equal(secondArgument, second);
133 | }
134 | });
135 |
136 | bb.on('begin', (first, second) => {
137 | firstArgument = first;
138 | secondArgument = second;
139 | });
140 |
141 | bb.run(() => bb.schedule('one', null, () => {}));
142 | bb.run(() => bb.schedule('one', null, () => {}));
143 | });
144 |
145 | // blah
146 | QUnit.test('events should work with multiple callbacks', function(assert) {
147 | assert.expect(2);
148 | let firstCalled = false;
149 | let secondCalled = false;
150 |
151 | let bb = new Backburner(['one']);
152 |
153 | function first() {
154 | firstCalled = true;
155 | }
156 |
157 | function second() {
158 | secondCalled = true;
159 | }
160 |
161 | bb.on('end', first);
162 | bb.on('end', second);
163 |
164 | bb.run(() => bb.schedule('one', null, () => {}));
165 |
166 | assert.equal(secondCalled, true);
167 | assert.equal(firstCalled, true);
168 | });
169 |
170 | QUnit.test('off should unregister specific callback', function(assert) {
171 | assert.expect(2);
172 | let firstCalled = false;
173 | let secondCalled = false;
174 |
175 | let bb = new Backburner(['one']);
176 |
177 | function first() {
178 | firstCalled = true;
179 | }
180 |
181 | function second() {
182 | secondCalled = true;
183 | }
184 |
185 | bb.on('end', first);
186 | bb.on('end', second);
187 |
188 | bb.off('end', first);
189 |
190 | bb.run(() => bb.schedule('one', null, () => {}));
191 |
192 | assert.equal(secondCalled, true);
193 | assert.equal(firstCalled, false);
194 | });
195 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /* globals module, require */
2 | 'use strict';
3 | const MergeTrees = require('broccoli-merge-trees');
4 | const Funnel = require('broccoli-funnel');
5 | const Rollup = require('broccoli-rollup');
6 | const resolve = require('rollup-plugin-node-resolve');
7 | const commonjs = require('rollup-plugin-commonjs');
8 | const path = require('path');
9 | const typescript = require('broccoli-typescript-compiler').default;
10 | const buble = require('rollup-plugin-buble');
11 | const fs = require('fs');
12 |
13 | const SOURCE_MAPPING_DATA_URL = '//# sourceMap' + 'pingURL=data:application/json;base64,';
14 |
15 | module.exports = function (app) {
16 | const src = new MergeTrees([
17 | new Funnel(__dirname + '/lib', {
18 | destDir: 'lib'
19 | }),
20 | new Funnel(__dirname + '/tests', {
21 | destDir: 'tests'
22 | })
23 | ]);
24 |
25 | const compiled = typescript(src, {
26 | throwOnError: process.env.EMBER_ENV === 'production',
27 | });
28 |
29 | const backburner = new Rollup(compiled, {
30 | rollup: {
31 | input: 'lib/index.js',
32 | output: {
33 | file: 'es6/backburner.js',
34 | format: 'es',
35 | sourcemap: true
36 | },
37 | format: 'es',
38 | plugins: [
39 | loadWithInlineMap()
40 | ]
41 | }
42 | });
43 |
44 | return new MergeTrees([
45 | backburner,
46 | new Rollup(compiled, {
47 | rollup: {
48 | input: 'lib/index.js',
49 | plugins: [
50 | loadWithInlineMap(),
51 | buble()
52 | ],
53 | output: [{
54 | file: 'named-amd/backburner.js',
55 | exports: 'named',
56 | format: 'amd',
57 | amd: { id: 'backburner' },
58 | sourcemap: true
59 | }, {
60 | file: 'backburner.js',
61 | format: 'cjs',
62 | sourcemap: true
63 | }]
64 | }
65 | }),
66 | new Rollup(compiled, {
67 | annotation: 'named-amd/tests.js',
68 | rollup: {
69 | input: 'tests/index.js',
70 | external: ['backburner'],
71 | plugins: [
72 | // just used to resolve lolex, which has a malformed `modules` entry point
73 | resolve({ module: false, main: true }),
74 | commonjs(),
75 | loadWithInlineMap(),
76 | buble()
77 | ],
78 | output: [{
79 | file: 'named-amd/tests.js',
80 | format: 'amd',
81 | amd: { id: 'backburner-tests' },
82 | sourcemap: true
83 | }]
84 | }
85 | }),
86 | new Funnel(path.dirname(require.resolve('qunit')), {
87 | annotation: 'tests/qunit.{js,css}',
88 | destDir: 'tests',
89 | files: ['qunit.css', 'qunit.js']
90 | }),
91 | new Funnel(path.dirname(require.resolve('loader.js')), {
92 | annotation: 'loader.js',
93 | destDir: '',
94 | files: ['loader.js']
95 | }),
96 | new Funnel(compiled, {
97 | destDir: '/',
98 | include: ['lib/**/*.d.ts'],
99 |
100 | getDestinationPath: function(relativePath) {
101 | let path = relativePath.substring(4); // Slice off lib
102 | if (path === 'index.d.ts') {
103 | return 'backburner.d.ts';
104 | }
105 | return path;
106 | }
107 | }),
108 | new Funnel(__dirname + '/tests', {
109 | destDir: 'tests',
110 | files: ['index.html']
111 | }),
112 | new Funnel(__dirname + "/bench", {
113 | destDir: "bench",
114 | files: ["index.html"],
115 | }),
116 | new Rollup(__dirname + "/bench", {
117 | rollup: {
118 | treeshake: false,
119 | input: "browser-bench.js",
120 | external: ["backburner"],
121 | plugins: [
122 | resolve(),
123 | commonjs(),
124 | loadWithInlineMap(),
125 | ],
126 | output: [
127 | {
128 | file: "bench/browser-bench.js",
129 | format: "amd",
130 | amd: { id: "browser-bench" },
131 | sourcemap: true,
132 | },
133 | ],
134 | },
135 | }),
136 | new Funnel(path.dirname(require.resolve("lodash")), {
137 | destDir: "bench",
138 | files: ["lodash.js"],
139 | }),
140 | ], {
141 | annotation: 'dist'
142 | });
143 | };
144 |
145 | function loadWithInlineMap() {
146 | return {
147 | load: function (id) {
148 | var code = fs.readFileSync(id, 'utf8');
149 | var result = {
150 | code: code,
151 | map: null
152 | };
153 | var index = code.lastIndexOf(SOURCE_MAPPING_DATA_URL);
154 | if (index === -1) {
155 | return result;
156 | }
157 | result.code = code.slice(0, index);
158 | result.map = parseSourceMap(code.slice(index + SOURCE_MAPPING_DATA_URL.length));
159 | result.file = id;
160 | return result;
161 | }
162 | };
163 | }
164 |
165 | function parseSourceMap(base64) {
166 | return JSON.parse(new Buffer(base64, 'base64').toString('utf8'));
167 | }
168 |
--------------------------------------------------------------------------------
/tests/run-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/run');
4 |
5 | QUnit.test('when passed a function', function(assert) {
6 | assert.expect(1);
7 |
8 | let bb = new Backburner(['one']);
9 | let functionWasCalled = false;
10 |
11 | bb.run(() => functionWasCalled = true);
12 |
13 | assert.ok(functionWasCalled, 'function was called');
14 | });
15 |
16 | QUnit.test('when passed a target and method', function(assert) {
17 | assert.expect(2);
18 |
19 | let bb = new Backburner(['one']);
20 | let functionWasCalled = false;
21 |
22 | bb.run({zomg: 'hi'}, function() {
23 | assert.equal(this.zomg, 'hi', 'the target was properly set');
24 | functionWasCalled = true;
25 | });
26 |
27 | assert.ok(functionWasCalled, 'function was called');
28 | });
29 |
30 | QUnit.test('when passed a target, method, and arguments', function(assert) {
31 | assert.expect(5);
32 |
33 | let bb = new Backburner(['one']);
34 | let functionWasCalled = false;
35 |
36 | bb.run({zomg: 'hi'}, function(a, b, c) {
37 | assert.equal(this.zomg, 'hi', 'the target was properly set');
38 | assert.equal(a, 1, 'the first arguments was passed in');
39 | assert.equal(b, 2, 'the second arguments was passed in');
40 | assert.equal(c, 3, 'the third arguments was passed in');
41 | functionWasCalled = true;
42 | }, 1, 2, 3);
43 |
44 | assert.ok(functionWasCalled, 'function was called');
45 | });
46 |
47 | QUnit.test('nesting run loops preserves the stack', function(assert) {
48 | assert.expect(10);
49 |
50 | let bb = new Backburner(['one']);
51 | let outerBeforeFunctionWasCalled = false;
52 | let middleBeforeFunctionWasCalled = false;
53 | let innerFunctionWasCalled = false;
54 | let middleAfterFunctionWasCalled = false;
55 | let outerAfterFunctionWasCalled = false;
56 |
57 | bb.run(() => {
58 | bb.schedule('one', () => {
59 | outerBeforeFunctionWasCalled = true;
60 | });
61 |
62 | bb.run(() => {
63 | bb.schedule('one', () => {
64 | middleBeforeFunctionWasCalled = true;
65 | });
66 |
67 | bb.run(() => {
68 | bb.schedule('one', () => {
69 | innerFunctionWasCalled = true;
70 | });
71 | assert.ok(!innerFunctionWasCalled, 'function is deferred');
72 | });
73 | assert.ok(innerFunctionWasCalled, 'function is called');
74 |
75 | bb.schedule('one', () => {
76 | middleAfterFunctionWasCalled = true;
77 | });
78 |
79 | assert.ok(!middleBeforeFunctionWasCalled, 'function is deferred');
80 | assert.ok(!middleAfterFunctionWasCalled, 'function is deferred');
81 | });
82 |
83 | assert.ok(middleBeforeFunctionWasCalled, 'function is called');
84 | assert.ok(middleAfterFunctionWasCalled, 'function is called');
85 |
86 | bb.schedule('one', () => {
87 | outerAfterFunctionWasCalled = true;
88 | });
89 |
90 | assert.ok(!outerBeforeFunctionWasCalled, 'function is deferred');
91 | assert.ok(!outerAfterFunctionWasCalled, 'function is deferred');
92 | });
93 |
94 | assert.ok(outerBeforeFunctionWasCalled, 'function is called');
95 | assert.ok(outerAfterFunctionWasCalled, 'function is called');
96 | });
97 |
98 | QUnit.test('runs can be nested', function(assert) {
99 | assert.expect(2);
100 |
101 | let bb = new Backburner(['one']);
102 | let step = 0;
103 |
104 | bb.run(() => {
105 | assert.equal(step++, 0);
106 |
107 | bb.run(() => {
108 | assert.equal(step++, 1);
109 | });
110 | });
111 | });
112 |
113 | QUnit.test('run returns value', function(assert) {
114 | let bb = new Backburner(['one']);
115 | let value = bb.run(() => 'hi');
116 |
117 | assert.equal(value, 'hi');
118 | });
119 |
120 | QUnit.test('onError', function(assert) {
121 | assert.expect(1);
122 |
123 | function onError(error) {
124 | assert.equal('QUnit.test error', error.message);
125 | }
126 |
127 | let bb = new Backburner(['errors'], {
128 | onError: onError
129 | });
130 |
131 | bb.run(() => {
132 | throw new Error('QUnit.test error');
133 | });
134 | });
135 |
136 | QUnit.test('onError set after start', function(assert) {
137 | assert.expect(2);
138 |
139 | let bb = new Backburner(['errors']);
140 |
141 | bb.run(() => assert.ok(true));
142 |
143 | bb.options.onError = function(error) {
144 | assert.equal('QUnit.test error', error.message);
145 | };
146 |
147 | bb.run(() => { throw new Error('QUnit.test error'); });
148 | });
149 |
150 | QUnit.test('onError with target and action', function(assert) {
151 | assert.expect(3);
152 |
153 | let target = {};
154 |
155 | let bb = new Backburner(['errors'], {
156 | onErrorTarget: target,
157 | onErrorMethod: 'onerror'
158 | });
159 |
160 | bb.run(() => assert.ok(true));
161 |
162 | target['onerror'] = function(error) {
163 | assert.equal('QUnit.test error', error.message);
164 | };
165 |
166 | bb.run(() => { throw new Error('QUnit.test error'); });
167 |
168 | target['onerror'] = function() { };
169 |
170 | bb.run(() => { throw new Error('QUnit.test error'); });
171 |
172 | target['onerror'] = function(error) {
173 | assert.equal('QUnit.test error', error.message);
174 | };
175 |
176 | bb.run(() => { throw new Error('QUnit.test error'); });
177 | });
178 |
179 | QUnit.test('when [callback, string] args passed', function(assert) {
180 | assert.expect(2);
181 |
182 | let bb = new Backburner(['one']);
183 | let functionWasCalled = false;
184 |
185 | bb.run(function(name) {
186 | assert.equal(name, 'batman');
187 | functionWasCalled = true;
188 | }, 'batman');
189 |
190 | assert.ok(functionWasCalled, 'function was called');
191 | });
192 |
--------------------------------------------------------------------------------
/tests/join-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/join');
4 |
5 | function depth(bb) {
6 | return bb.instanceStack.length + (bb.currentInstance ? 1 : 0);
7 | }
8 |
9 | QUnit.test('outside of a run loop', function(assert) {
10 | assert.expect(4);
11 |
12 | let bb = new Backburner(['one']);
13 |
14 | assert.equal(depth(bb), 0);
15 | let result = bb.join(() => {
16 | assert.equal(depth(bb), 1);
17 | return 'result';
18 | });
19 | assert.equal(result, 'result');
20 | assert.equal(depth(bb), 0);
21 | });
22 |
23 | QUnit.test('inside of a run loop', function(assert) {
24 | assert.expect(4);
25 |
26 | let bb = new Backburner(['one']);
27 |
28 | assert.equal(depth(bb), 0);
29 | bb.run(() => {
30 | let result = bb.join(() => {
31 | assert.equal(depth(bb), 1);
32 | return 'result';
33 | });
34 | assert.equal(result, 'result');
35 | });
36 | assert.equal(depth(bb), 0);
37 | });
38 |
39 | QUnit.test('nested join calls', function(assert) {
40 | assert.expect(7);
41 |
42 | let bb = new Backburner(['one']);
43 |
44 | assert.equal(depth(bb), 0);
45 | bb.join(() => {
46 | assert.equal(depth(bb), 1);
47 | bb.join(() => {
48 | assert.equal(depth(bb), 1);
49 | bb.join(() => {
50 | assert.equal(depth(bb), 1);
51 | });
52 | assert.equal(depth(bb), 1);
53 | });
54 | assert.equal(depth(bb), 1);
55 | });
56 | assert.equal(depth(bb), 0);
57 | });
58 |
59 | QUnit.test('nested run loops', function(assert) {
60 | assert.expect(7);
61 |
62 | let bb = new Backburner(['one']);
63 |
64 | assert.equal(depth(bb), 0);
65 | bb.join(() => {
66 | assert.equal(depth(bb), 1);
67 | bb.run(() => {
68 | assert.equal(depth(bb), 2);
69 | bb.join(() => {
70 | assert.equal(depth(bb), 2);
71 | });
72 | assert.equal(depth(bb), 2);
73 | });
74 | assert.equal(depth(bb), 1);
75 | });
76 | assert.equal(depth(bb), 0);
77 | });
78 |
79 | QUnit.test('queue execution order', function(assert) {
80 | assert.expect(1);
81 |
82 | let bb = new Backburner(['one']);
83 | let items: number[] = [];
84 |
85 | bb.run(() => {
86 | items.push(0);
87 | bb.schedule('one', () => items.push(4));
88 | bb.join(() => {
89 | items.push(1);
90 | bb.schedule('one', () => items.push(5));
91 | items.push(2);
92 | });
93 | bb.schedule('one', () => items.push(6));
94 | items.push(3);
95 | });
96 | assert.deepEqual(items, [0, 1, 2, 3, 4, 5, 6]);
97 | });
98 |
99 | QUnit.test('without an onError run.join can be caught via `try`/`catch`', function(assert) {
100 | assert.expect(1);
101 |
102 | let bb = new Backburner(['errors']);
103 |
104 | assert.throws(() => {
105 | bb.join(() => {
106 | throw new Error('test error');
107 | });
108 | }, /test error/);
109 | });
110 |
111 | QUnit.test('with an onError which does not rethrow, when joining existing instance, can be caught via `try`/`catch`', function(assert) {
112 | assert.expect(1);
113 |
114 | let bb = new Backburner(['errors'], {
115 | onError(error) {
116 | assert.notOk(true, 'onError should not be called as the error from .join is handled by assert.throws');
117 | }
118 | });
119 |
120 | bb.run(() => {
121 | assert.throws(() => {
122 | bb.join(() => {
123 | throw new Error('test error');
124 | });
125 | }, /test error/, 'error from within .join can be caught with try/catch');
126 | });
127 | });
128 |
129 | QUnit.test('onError which does not rethrow is invoked (only once) when not joining an existing instance', function(assert) {
130 | assert.expect(1);
131 |
132 | function onError(error) {
133 | assert.equal('test error', error.message);
134 | }
135 |
136 | let bb = new Backburner(['errors'], {
137 | onError: onError
138 | });
139 |
140 | bb.join(() => {
141 | throw new Error('test error');
142 | });
143 | });
144 |
145 | QUnit.test('onError which does not rethrow is invoked (only once) when joining an existing instance', function(assert) {
146 | assert.expect(1);
147 |
148 | function onError(error) {
149 | assert.equal('test error', error.message);
150 | }
151 |
152 | let bb = new Backburner(['errors'], {
153 | onError: onError
154 | });
155 |
156 | bb.run(() => {
157 | bb.join(() => {
158 | throw new Error('test error');
159 | });
160 | });
161 | });
162 |
163 | QUnit.test('onError which does rethrow is invoked (only once) when not joining an existing instance', function(assert) {
164 | assert.expect(2);
165 |
166 | function onError(error) {
167 | assert.equal('test error', error.message);
168 | throw error;
169 | }
170 |
171 | let bb = new Backburner(['errors'], {
172 | onError: onError
173 | });
174 |
175 | assert.throws(() => {
176 | bb.join(() => {
177 | throw new Error('test error');
178 | });
179 | }, /test error/);
180 | });
181 |
182 | QUnit.test('onError which does rethrow is invoked (only once) when joining an existing instance', function(assert) {
183 | assert.expect(2);
184 |
185 | function onError(error) {
186 | assert.equal('test error', error.message);
187 | throw error;
188 | }
189 |
190 | let bb = new Backburner(['errors'], {
191 | onError: onError
192 | });
193 |
194 | assert.throws(() => {
195 | bb.run(() => {
196 | bb.join(() => {
197 | throw new Error('test error');
198 | });
199 | });
200 | }, /test error/);
201 | });
202 |
203 | QUnit.test('when [callback, string] args passed', function(assert) {
204 | assert.expect(2);
205 |
206 | let bb = new Backburner(['one']);
207 | let functionWasCalled = false;
208 |
209 | bb.join(function(name) {
210 | assert.equal(name, 'batman');
211 | functionWasCalled = true;
212 | }, 'batman');
213 |
214 | assert.ok(functionWasCalled, 'function was called');
215 | });
216 |
--------------------------------------------------------------------------------
/lib/backburner/queue.ts:
--------------------------------------------------------------------------------
1 | import { IQueueItem } from './interfaces';
2 | import {
3 | findItem,
4 | getOnError,
5 | getQueueItems,
6 | QUEUE_ITEM_LENGTH
7 | } from './utils';
8 |
9 | export const enum QUEUE_STATE {
10 | Pause = 1
11 | }
12 |
13 | export default class Queue {
14 | private name: string;
15 | private globalOptions: any;
16 | private options: any;
17 | private _queueBeingFlushed: any[] = [];
18 | private targetQueues = new Map();
19 | private index = 0;
20 | private _queue: any[] = [];
21 |
22 | constructor(name: string, options: any = {}, globalOptions: any = {}) {
23 | this.name = name;
24 | this.options = options;
25 | this.globalOptions = globalOptions;
26 | }
27 |
28 | public stackFor(index) {
29 | if (index < this._queue.length) {
30 | let entry = this._queue[(index * QUEUE_ITEM_LENGTH) + 3];
31 | if (entry) {
32 | return entry.stack;
33 | } else {
34 | return null;
35 | }
36 | }
37 | }
38 |
39 | public consoleTaskFor(index, inQueueBeingFlushed = false) {
40 | let q = inQueueBeingFlushed ? this._queueBeingFlushed : this._queue;
41 | if (index < q.length) {
42 | return q[(index * QUEUE_ITEM_LENGTH) + 4];
43 | }
44 | }
45 |
46 | public flush(sync?: Boolean) {
47 | let { before, after } = this.options;
48 | let target;
49 | let method;
50 | let args;
51 | let errorRecordedForStack;
52 | let consoleTask;
53 |
54 | this.targetQueues.clear();
55 | if (this._queueBeingFlushed.length === 0) {
56 | this._queueBeingFlushed = this._queue;
57 | this._queue = [];
58 | }
59 |
60 | if (before !== undefined) {
61 | before();
62 | }
63 |
64 | let invoke;
65 | let queueItems = this._queueBeingFlushed;
66 | if (queueItems.length > 0) {
67 | let onError = getOnError(this.globalOptions);
68 | invoke = onError ? this.invokeWithOnError : this.invoke;
69 |
70 | for (let i = this.index; i < queueItems.length; i += QUEUE_ITEM_LENGTH) {
71 | this.index += QUEUE_ITEM_LENGTH;
72 |
73 | method = queueItems[i + 1];
74 | // method could have been nullified / canceled during flush
75 | if (method !== null) {
76 | //
77 | // ** Attention intrepid developer **
78 | //
79 | // To find out the stack of this task when it was scheduled onto
80 | // the run loop, add the following to your app.js:
81 | //
82 | // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production.
83 | //
84 | // Once that is in place, when you are at a breakpoint and navigate
85 | // here in the stack explorer, you can look at `errorRecordedForStack.stack`,
86 | // which will be the captured stack when this job was scheduled.
87 | //
88 | // One possible long-term solution is the following Chrome issue:
89 | // https://bugs.chromium.org/p/chromium/issues/detail?id=332624
90 | //
91 | target = queueItems[i];
92 | args = queueItems[i + 2];
93 | errorRecordedForStack = queueItems[i + 3]; // Debugging assistance
94 | consoleTask = queueItems[i + 4];
95 | if(consoleTask){
96 | consoleTask.run(invoke.bind(this, target, method, args, onError, errorRecordedForStack))
97 | }else{
98 | invoke(target, method, args, onError, errorRecordedForStack)
99 | }
100 | }
101 |
102 | if (this.index !== this._queueBeingFlushed.length &&
103 | this.globalOptions.mustYield && this.globalOptions.mustYield()) {
104 | return QUEUE_STATE.Pause;
105 | }
106 | }
107 | }
108 |
109 | if (after !== undefined) {
110 | after();
111 | }
112 |
113 | this._queueBeingFlushed.length = 0;
114 | this.index = 0;
115 | if (sync !== false && this._queue.length > 0) {
116 | // check if new items have been added
117 | this.flush(true);
118 | }
119 | }
120 |
121 | public hasWork() {
122 | return this._queueBeingFlushed.length > 0 || this._queue.length > 0;
123 | }
124 |
125 | public cancel({ target, method }) {
126 | let queue = this._queue;
127 | let targetQueueMap = this.targetQueues.get(target);
128 |
129 | if (targetQueueMap !== undefined) {
130 | targetQueueMap.delete(method);
131 | }
132 |
133 | let index = findItem(target, method, queue);
134 |
135 | if (index > -1) {
136 | queue[index + 1] = null;
137 | return true;
138 | }
139 |
140 | // if not found in current queue
141 | // could be in the queue that is being flushed
142 | queue = this._queueBeingFlushed;
143 |
144 | index = findItem(target, method, queue);
145 | if (index > -1) {
146 | queue[index + 1] = null;
147 | return true;
148 | }
149 |
150 | return false;
151 | }
152 |
153 | public push(target, method, args, stack, consoleTask): { queue: Queue, target, method } {
154 | this._queue.push(target, method, args, stack, consoleTask);
155 |
156 | return {
157 | queue: this,
158 | target,
159 | method
160 | };
161 | }
162 |
163 | public pushUnique(target, method, args, stack, consoleTask): { queue: Queue, target, method } {
164 | let localQueueMap = this.targetQueues.get(target);
165 |
166 | if (localQueueMap === undefined) {
167 | localQueueMap = new Map();
168 | this.targetQueues.set(target, localQueueMap);
169 | }
170 |
171 | let index = localQueueMap.get(method);
172 | if (index === undefined) {
173 | let queueIndex = this._queue.push(target, method, args, stack, consoleTask) - QUEUE_ITEM_LENGTH;
174 | localQueueMap.set(method, queueIndex);
175 | } else {
176 | let queue = this._queue;
177 | queue[index + 2] = args; // replace args
178 | queue[index + 3] = stack; // replace stack
179 | queue[index + 3] = consoleTask; // replace consoleTask
180 | }
181 |
182 | return {
183 | queue: this,
184 | target,
185 | method
186 | };
187 | }
188 |
189 | public _getDebugInfo(debugEnabled: boolean): IQueueItem[] | undefined {
190 | if (debugEnabled) {
191 | let debugInfo: IQueueItem[] = getQueueItems(this._queue, QUEUE_ITEM_LENGTH);
192 |
193 | return debugInfo;
194 | }
195 |
196 | return undefined;
197 | }
198 |
199 | private invoke(target, method, args /*, onError, errorRecordedForStack */) {
200 | if (args === undefined) {
201 | method.call(target);
202 | } else {
203 | method.apply(target, args);
204 | }
205 | }
206 |
207 | private invokeWithOnError(target, method, args, onError, errorRecordedForStack) {
208 | try {
209 | if (args === undefined) {
210 | method.call(target);
211 | } else {
212 | method.apply(target, args);
213 | }
214 | } catch (error) {
215 | onError(error, errorRecordedForStack);
216 | }
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/tests/debug-info-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 | import MockStableError, {
3 | overrideError,
4 | pushStackTrace,
5 | resetError,
6 | } from './utils/mock-stable-error';
7 |
8 | QUnit.module('tests/debug-info', {
9 | beforeEach: function() {
10 | // @ts-ignore
11 | overrideError(MockStableError);
12 | },
13 |
14 | afterEach: function() {
15 | resetError();
16 | }
17 | });
18 |
19 | QUnit.test('getDebugInfo returns undefined when DEBUG = false', function(assert) {
20 | assert.expect(1);
21 |
22 | let debugInfo;
23 | let bb = new Backburner(['one']);
24 |
25 | bb.run(function() {
26 | debugInfo = bb.getDebugInfo();
27 |
28 | assert.equal(debugInfo, undefined, 'DebugInfo is undefined when DEBUG = false');
29 | });
30 | });
31 |
32 | QUnit.test('getDebugInfo returns debugInfo using later when DEBUG = true', function(assert) {
33 | assert.expect(1);
34 |
35 | let debugInfo;
36 | let target1 = { one: true };
37 | let target2 = { two: true };
38 | let method = () => {};
39 | let arg1 = 1;
40 | let arg2 = 2;
41 | let twoStack = pushStackTrace('Two stack');
42 | let oneStack = pushStackTrace('One stack');
43 | let bb = new Backburner(['one']);
44 |
45 | bb.DEBUG = true;
46 |
47 | bb.run(function() {
48 | bb.later(target1, method, arg1, 1000);
49 | bb.later(target2, method, arg1, arg2, 1000);
50 |
51 | debugInfo = bb.getDebugInfo();
52 |
53 | resetError();
54 |
55 | assert.deepEqual(debugInfo.timers,
56 | [
57 | {
58 | args: [arg1],
59 | method,
60 | stack: oneStack,
61 | target: target1
62 | },
63 | {
64 | args: [arg1, arg2],
65 | method,
66 | stack: twoStack,
67 | target: target2
68 | }
69 | ]
70 | , 'debugInfo is output');
71 | });
72 | });
73 |
74 | QUnit.test('getDebugInfo returns debugInfo using throttle when DEBUG = true', function(assert) {
75 | assert.expect(1);
76 |
77 | let debugInfo;
78 | let target1 = { one: true };
79 | let target2 = { two: true };
80 | let method = () => {};
81 | let arg1 = 1;
82 | let arg2 = 2;
83 | let twoStack = pushStackTrace('Two stack');
84 | let oneStack = pushStackTrace('One stack');
85 | let bb = new Backburner(['one']);
86 |
87 | bb.DEBUG = true;
88 |
89 | bb.run(function() {
90 | bb.throttle(target1, method, arg1, 1000, false);
91 | bb.throttle(target2, method, arg1, arg2, 1000, false);
92 |
93 | debugInfo = bb.getDebugInfo();
94 |
95 | resetError();
96 |
97 | assert.deepEqual(debugInfo.timers,
98 | [
99 | {
100 | args: [arg1],
101 | method,
102 | stack: oneStack,
103 | target: target1
104 | },
105 | {
106 | args: [arg1, arg2],
107 | method,
108 | stack: twoStack,
109 | target: target2
110 | }
111 | ]
112 | , 'debugInfo is output');
113 | });
114 | });
115 |
116 | QUnit.test('getDebugInfo returns debugInfo using debounce when DEBUG = true', function(assert) {
117 | assert.expect(1);
118 |
119 | let debugInfo;
120 | let target1 = { one: true };
121 | let target2 = { two: true };
122 | let method = () => {};
123 | let arg1 = 1;
124 | let arg2 = 2;
125 | let twoStack = pushStackTrace('Two stack');
126 | let oneStack = pushStackTrace('One stack');
127 | let bb = new Backburner(['one']);
128 |
129 | bb.DEBUG = true;
130 |
131 | bb.run(function() {
132 | bb.debounce(target1, method, arg1, 1000);
133 | bb.debounce(target2, method, arg1, arg2, 1000);
134 |
135 | debugInfo = bb.getDebugInfo();
136 |
137 | resetError();
138 |
139 | assert.deepEqual(debugInfo.timers,
140 | [
141 | {
142 | args: [arg1],
143 | method,
144 | stack: oneStack,
145 | target: target1
146 | },
147 | {
148 | args: [arg1, arg2],
149 | method,
150 | stack: twoStack,
151 | target: target2
152 | }
153 | ]
154 | , 'debugInfo is output');
155 | });
156 | });
157 |
158 | QUnit.test('getDebugInfo returns debugInfo using when DEBUG = true', function(assert) {
159 | assert.expect(1);
160 |
161 | let debugInfo;
162 | let target1 = { one: true };
163 | let target2 = { two: true };
164 | let method = () => {};
165 | let arg1 = 1;
166 | let arg2 = 2;
167 | let twoStack = pushStackTrace('Two stack');
168 | let oneStack = pushStackTrace('One stack');
169 | let bb = new Backburner(['one', 'two']);
170 |
171 | bb.DEBUG = true;
172 |
173 | bb.run(function() {
174 | bb.schedule('one', target1, method, arg1);
175 | bb.schedule('two', target2, method, arg1, arg2);
176 |
177 | debugInfo = bb.getDebugInfo();
178 |
179 | resetError();
180 |
181 | assert.deepEqual(debugInfo.instanceStack,
182 | [
183 | {
184 | one: [
185 | {
186 | args: [arg1],
187 | method,
188 | stack: oneStack,
189 | target: target1
190 | }
191 | ],
192 | two: [
193 | {
194 | args: [arg1, arg2],
195 | method,
196 | stack: twoStack,
197 | target: target2
198 | }
199 | ]
200 | }
201 | ]
202 | , 'debugInfo is output');
203 | });
204 | });
205 |
206 | QUnit.test('getDebugInfo returns debugInfo when DEBUG = true in nested run', function(assert) {
207 | assert.expect(1);
208 |
209 | let debugInfo;
210 | let method = () => {};
211 | let twoStack = pushStackTrace('Two stack');
212 | let oneStack = pushStackTrace('One stack');
213 | let fourStack = pushStackTrace('Four stack');
214 | let threeStack = pushStackTrace('Three stack');
215 | let bb = new Backburner(['one', 'two', 'three', 'four']);
216 |
217 | bb.DEBUG = true;
218 |
219 | bb.run(function() {
220 | bb.schedule('one', method);
221 | bb.schedule('two', method);
222 |
223 | bb.run(function() {
224 | bb.schedule('three', method);
225 | bb.schedule('four', method);
226 |
227 | debugInfo = bb.getDebugInfo();
228 |
229 | resetError();
230 |
231 | assert.deepEqual(debugInfo.instanceStack,
232 | [
233 | {
234 | four: [
235 | {
236 | args: undefined,
237 | method,
238 | stack: fourStack,
239 | target: null
240 | }
241 | ],
242 | one: [],
243 | three: [
244 | {
245 | args: undefined,
246 | method,
247 | stack: threeStack,
248 | target: null
249 | }
250 | ],
251 | two: []
252 | },
253 | {
254 | four: [],
255 | one: [
256 | {
257 | args: undefined,
258 | method,
259 | stack: oneStack,
260 | target: null
261 | }
262 | ],
263 | three: [],
264 | two: [
265 | {
266 | args: undefined,
267 | method,
268 | stack: twoStack,
269 | target: null
270 | }
271 | ]
272 | }
273 | ]
274 | , 'debugInfo is output');
275 | });
276 | });
277 | });
278 |
279 | QUnit.test('autorun', function(assert) {
280 | let done = assert.async();
281 | let bb = new Backburner(['one']);
282 | let autorunStack = pushStackTrace('Autorun stack');
283 | pushStackTrace('Schedule stack');
284 |
285 | bb.DEBUG = true;
286 |
287 | bb.schedule('one', null, () => {
288 | setTimeout(() => {
289 | assert.equal(bb.getDebugInfo()!.autorun, null);
290 | done();
291 | });
292 | });
293 |
294 | assert.equal(bb.getDebugInfo()!.autorun!.stack, autorunStack);
295 | });
296 |
--------------------------------------------------------------------------------
/tests/defer-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 | let originalDateValueOf = Date.prototype.valueOf;
3 |
4 | QUnit.module('tests/defer', {
5 | afterEach() {
6 | Date.prototype.valueOf = originalDateValueOf;
7 | }
8 | });
9 |
10 | QUnit.test('when passed a function', function(assert) {
11 | assert.expect(1);
12 |
13 | let bb = new Backburner(['one']);
14 | let functionWasCalled = false;
15 |
16 | bb.run(() => {
17 | bb.schedule('one', () => functionWasCalled = true);
18 | });
19 |
20 | assert.ok(functionWasCalled, 'function was called');
21 | });
22 |
23 | QUnit.test('when passed a target and method', function(assert) {
24 | assert.expect(2);
25 |
26 | let bb = new Backburner(['one']);
27 | let functionWasCalled = false;
28 |
29 | bb.run(() => {
30 | bb.schedule('one', { zomg: 'hi' }, function() {
31 | assert.equal(this.zomg, 'hi', 'the target was properly set');
32 | functionWasCalled = true;
33 | });
34 | });
35 |
36 | assert.ok(functionWasCalled, 'function was called');
37 | });
38 |
39 | QUnit.test('when [queueName, callback, string] args passed', function(assert) {
40 | assert.expect(2);
41 |
42 | let bb = new Backburner(['one']);
43 | let functionWasCalled = false;
44 |
45 | bb.run(() => {
46 | bb.schedule('one', function(name) {
47 | assert.equal(name, 'batman');
48 | functionWasCalled = true;
49 | }, 'batman');
50 | });
51 |
52 | assert.ok(functionWasCalled, 'function was called');
53 | });
54 |
55 | QUnit.test('when passed a target and method name', function(assert) {
56 | assert.expect(2);
57 |
58 | let bb = new Backburner(['one']);
59 | let functionWasCalled = false;
60 | let targetObject = {
61 | zomg: 'hi',
62 | checkFunction() {
63 | assert.equal(this.zomg, 'hi', 'the target was properly set');
64 | functionWasCalled = true;
65 | }
66 | };
67 |
68 | bb.run(() => bb.schedule('one', targetObject, 'checkFunction'));
69 |
70 | assert.ok(functionWasCalled, 'function was called');
71 | });
72 |
73 | QUnit.test('throws when passed a null method', function(assert) {
74 | assert.expect(1);
75 |
76 | function onError(error) {
77 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
78 | }
79 |
80 | let bb = new Backburner(['deferErrors'], {
81 | onError
82 | });
83 |
84 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, null));
85 | });
86 |
87 | QUnit.test('throws when passed an undefined method', function(assert) {
88 | assert.expect(1);
89 |
90 | function onError(error) {
91 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
92 | }
93 |
94 | let bb = new Backburner(['deferErrors'], {
95 | onError
96 | });
97 |
98 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, undefined));
99 | });
100 |
101 | QUnit.test('throws when passed an method name that does not exists on the target', function(assert) {
102 | assert.expect(1);
103 |
104 | function onError(error) {
105 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
106 | }
107 |
108 | let bb = new Backburner(['deferErrors'], {
109 | onError
110 | });
111 |
112 | bb.run(() => bb.schedule('deferErrors', { zomg: 'hi' }, 'checkFunction'));
113 | });
114 |
115 | QUnit.test('when passed a target, method, and arguments', function(assert) {
116 | assert.expect(5);
117 |
118 | let bb = new Backburner(['one']);
119 | let functionWasCalled = false;
120 |
121 | bb.run(() => {
122 | bb.schedule('one', { zomg: 'hi' }, function(a, b, c) {
123 | assert.equal(this.zomg, 'hi', 'the target was properly set');
124 | assert.equal(a, 1, 'the first arguments was passed in');
125 | assert.equal(b, 2, 'the second arguments was passed in');
126 | assert.equal(c, 3, 'the third arguments was passed in');
127 | functionWasCalled = true;
128 | }, 1, 2, 3);
129 | });
130 |
131 | assert.ok(functionWasCalled, 'function was called');
132 | });
133 |
134 | QUnit.test('when passed same function twice', function(assert) {
135 | assert.expect(1);
136 |
137 | let bb = new Backburner(['one']);
138 | let i = 0;
139 |
140 | function deferMethod() {
141 | i++;
142 | }
143 |
144 | bb.run(() => {
145 | bb.schedule('one', deferMethod);
146 | bb.schedule('one', deferMethod);
147 | });
148 |
149 | assert.equal(i, 2, 'function was called twice');
150 | });
151 |
152 | QUnit.test('when passed same function twice with arguments', function(assert) {
153 | assert.expect(2);
154 |
155 | let bb = new Backburner(['one']);
156 | let i = 0;
157 | let argObj = {first : 1};
158 |
159 | function deferMethod() {
160 | assert.equal(this['first'], 1, 'the target property was set');
161 | }
162 |
163 | bb.run(() => {
164 | bb.schedule('one', argObj, deferMethod);
165 | bb.schedule('one', argObj, deferMethod);
166 | });
167 | });
168 |
169 | QUnit.test('when passed same function twice with same arguments and same target', function(assert) {
170 | assert.expect(7);
171 |
172 | let bb = new Backburner(['one']);
173 | let i = 0;
174 |
175 | function deferMethod(a, b) {
176 | i++;
177 | assert.equal(a, 1, 'First argument is set twice');
178 | assert.equal(b, 2, 'Second argument is set twice');
179 | assert.equal(this['first'], 1, 'the target property was set');
180 | }
181 |
182 | let argObj = { first: 1 };
183 |
184 | bb.run(() => {
185 | bb.schedule('one', argObj, deferMethod, 1, 2);
186 | bb.schedule('one', argObj, deferMethod, 1, 2);
187 | });
188 |
189 | assert.equal(i, 2, 'function was called twice');
190 | });
191 |
192 | QUnit.test('when passed same function twice with same target and different arguments', function(assert) {
193 | assert.expect(7);
194 |
195 | let bb = new Backburner(['one']);
196 | let i = 0;
197 |
198 | function deferMethod(a, b) {
199 | i++;
200 | if (i === 1) {
201 | assert.equal(a, 1, 'First argument set during first call');
202 | } else {
203 | assert.equal(a, 3, 'First argument set during second call');
204 | }
205 | assert.equal(b, 2, 'Second argument remains same');
206 | assert.equal(this['first'], 1, 'the target property was set');
207 | }
208 |
209 | let argObj = { first: 1 };
210 |
211 | bb.run(() => {
212 | bb.schedule('one', argObj, deferMethod, 1, 2);
213 | bb.schedule('one', argObj, deferMethod, 3, 2);
214 | });
215 |
216 | assert.equal(i, 2, 'function was called twice');
217 | });
218 |
219 | QUnit.test('when passed same function twice with different target and different arguments', function(assert) {
220 | assert.expect(7);
221 |
222 | let bb = new Backburner(['one']);
223 | let i = 0;
224 |
225 | function deferMethod(a, b) {
226 | i++;
227 | if (i === 1) {
228 | assert.equal(a, 1, 'First argument set during first call');
229 | } else {
230 | assert.equal(a, 3, 'First argument set during second call');
231 | }
232 | assert.equal(b, 2, 'Second argument remains same');
233 | assert.equal(this['first'], 1, 'the target property was set');
234 | }
235 |
236 | let argObj = {first: 1};
237 |
238 | bb.run(() => {
239 | bb.schedule('one', { first: 1 }, deferMethod, 1, 2);
240 | bb.schedule('one', { first: 1 }, deferMethod, 3, 2);
241 | });
242 |
243 | assert.equal(i, 2, 'function was called twice');
244 | });
245 |
246 | QUnit.test('onError', function(assert) {
247 | assert.expect(1);
248 |
249 | function onError(error) {
250 | assert.equal('QUnit.test error', error.message);
251 | }
252 |
253 | let bb = new Backburner(['errors'], {
254 | onError
255 | });
256 |
257 | bb.run(() => {
258 | bb.schedule('errors', () => {
259 | throw new Error('QUnit.test error');
260 | });
261 | });
262 | });
263 |
--------------------------------------------------------------------------------
/tests/cancel-test.ts:
--------------------------------------------------------------------------------
1 |
2 | import Backburner from 'backburner';
3 |
4 | QUnit.module('tests/cancel');
5 |
6 | QUnit.test('scheduleOnce', function(assert) {
7 | assert.expect(3);
8 |
9 | let bb = new Backburner(['one']);
10 | let functionWasCalled = false;
11 |
12 | bb.run(() => {
13 | let timer = bb.scheduleOnce('one', () => functionWasCalled = true);
14 |
15 | assert.ok(timer, 'Timer object was returned');
16 | assert.ok(bb.cancel(timer), 'Cancel returned true');
17 | assert.ok(!functionWasCalled, 'function was not called');
18 | });
19 | });
20 |
21 | QUnit.test('cancelling does not affect future scheduleOnce calls', function(assert) {
22 | assert.expect(5);
23 |
24 | let bb = new Backburner(['queueName']);
25 | const f1Calls: string[] = [];
26 | const f2Calls: string[] = [];
27 | const f3Calls: string[] = [];
28 | const f1 = (arg: string) => f1Calls.push(arg);
29 | const f2 = (arg: string) => f2Calls.push(arg);
30 | const f3 = (arg: string) => f3Calls.push(arg);
31 |
32 | bb.run(() => {
33 | const toCancel = bb.scheduleOnce('queueName', null, f1, 'f1 cancelled schedule');
34 | bb.scheduleOnce('queueName', null, f2, 'f2 first schedule');
35 | bb.scheduleOnce('queueName', null, f3, 'f3 first schedule');
36 | bb.cancel(toCancel);
37 | bb.scheduleOnce('queueName', null, f2, 'f2 second schedule');
38 | });
39 |
40 | assert.equal(f1Calls.length, 0, 'f1 was not called')
41 | assert.equal(f2Calls.length, 1, 'f2 was called once')
42 | assert.equal(f3Calls.length, 1, 'f3 was called once')
43 | assert.deepEqual(f2Calls, ['f2 second schedule'], 'f2 received the correct argument')
44 | assert.deepEqual(f3Calls, ['f3 first schedule'], 'f3 received the correct argument')
45 | });
46 |
47 | QUnit.test('setTimeout', function(assert) {
48 | assert.expect(5);
49 | let done = assert.async();
50 |
51 | let called = false;
52 | let bb = new Backburner(['one'], {
53 | onBegin() {
54 | called = true;
55 | }
56 | });
57 |
58 | let functionWasCalled = false;
59 | let timer = bb.later(() => functionWasCalled = true);
60 |
61 | assert.ok(timer, 'Timer object was returned');
62 | assert.ok(bb.cancel(timer), 'Cancel returned true');
63 | assert.ok(!called, 'onBegin was not called');
64 |
65 | setTimeout(() => {
66 | assert.ok(!functionWasCalled, 'function was not called');
67 | assert.ok(!called, 'onBegin was not called');
68 | done();
69 | }, 0);
70 | });
71 |
72 | QUnit.test('setTimeout with multiple pending', function(assert) {
73 | assert.expect(7);
74 |
75 | let done = assert.async();
76 | let called = false;
77 | let bb = new Backburner(['one'], {
78 | onBegin() {
79 | called = true;
80 | }
81 | });
82 | let function1WasCalled = false;
83 | let function2WasCalled = false;
84 |
85 | let timer1 = bb.later(() => function1WasCalled = true);
86 | let timer2 = bb.later(() => function2WasCalled = true);
87 |
88 | assert.ok(timer1, 'Timer object 2 was returned');
89 | assert.ok(bb.cancel(timer1), 'Cancel for timer 1 returned true');
90 | assert.ok(timer2, 'Timer object 2 was returned');
91 | assert.ok(!called, 'onBegin was not called');
92 |
93 | setTimeout(() => {
94 | assert.ok(!function1WasCalled, 'function 1 was not called');
95 | assert.ok(function2WasCalled, 'function 2 was called');
96 | assert.ok(called, 'onBegin was called');
97 |
98 | done();
99 | }, 10);
100 | });
101 |
102 | QUnit.test('setTimeout and creating a new later', function(assert) {
103 | assert.expect(7);
104 | let done = assert.async();
105 | let called = false;
106 | let bb = new Backburner(['one'], {
107 | onBegin() {
108 | called = true;
109 | }
110 | });
111 | let function1WasCalled = false;
112 | let function2WasCalled = false;
113 |
114 | let timer1 = bb.later(() => function1WasCalled = true, 0);
115 |
116 | assert.ok(timer1, 'Timer object 2 was returned');
117 | assert.ok(bb.cancel(timer1), 'Cancel for timer 1 returned true');
118 |
119 | let timer2 = bb.later(() => function2WasCalled = true, 1);
120 |
121 | assert.ok(timer2, 'Timer object 2 was returned');
122 | assert.ok(!called, 'onBegin was not called');
123 |
124 | setTimeout(() => {
125 | assert.ok(!function1WasCalled, 'function 1 was not called');
126 | assert.ok(function2WasCalled, 'function 2 was called');
127 | assert.ok(called, 'onBegin was called');
128 | done();
129 | }, 50);
130 | });
131 |
132 | QUnit.test('cancelTimers', function(assert) {
133 | assert.expect(8);
134 | let done = assert.async();
135 |
136 | let bb = new Backburner(['one']);
137 | let laterWasCalled = false;
138 | let debounceWasCalled = false;
139 | let throttleWasCalled = false;
140 |
141 | let timer1 = bb.later(() => laterWasCalled = true, 0);
142 | let timer2 = bb.debounce(() => debounceWasCalled = true, 0);
143 | let timer3 = bb.throttle(() => throttleWasCalled = true, 0, false);
144 |
145 | assert.ok(timer1, 'Timer object was returned');
146 | assert.ok(timer2, 'Timer object was returned');
147 | assert.ok(timer3, 'Timer object was returned');
148 | assert.ok(bb.hasTimers(), 'bb has scheduled timer');
149 |
150 | bb.cancelTimers();
151 |
152 | setTimeout(function() {
153 | assert.ok(!bb.hasTimers(), 'bb has no scheduled timer');
154 | assert.ok(!laterWasCalled, 'later function was not called');
155 | assert.ok(!debounceWasCalled, 'debounce function was not called');
156 | assert.ok(!throttleWasCalled, 'throttle function was not called');
157 | done();
158 | }, 100);
159 | });
160 |
161 | QUnit.test('cancel during flush', function(assert) {
162 | assert.expect(1);
163 |
164 | let bb = new Backburner(['one']);
165 | let functionWasCalled = false;
166 |
167 | bb.run(() => {
168 | let timer1 = bb.scheduleOnce('one', () => bb.cancel(timer2));
169 | let timer2 = bb.scheduleOnce('one', () => functionWasCalled = true);
170 | });
171 |
172 | assert.ok(!functionWasCalled, 'function was not called');
173 | });
174 |
175 | QUnit.test('with target', function(assert) {
176 | assert.expect(3);
177 |
178 | let obj = {
179 | ___FOO___: 1
180 | };
181 |
182 | let bb = new Backburner(['action']);
183 |
184 | let wasCalled = 0;
185 |
186 | function fn() {
187 | wasCalled++;
188 | }
189 |
190 | bb.run(() => {
191 | let timer = bb.scheduleOnce('action', obj, fn);
192 |
193 | assert.equal(wasCalled, 0);
194 |
195 | bb.cancel(timer);
196 | bb.scheduleOnce('action', obj, fn);
197 |
198 | assert.equal(wasCalled, 0);
199 | });
200 |
201 | assert.equal(wasCalled, 1);
202 | });
203 |
204 | QUnit.test('no target', function(assert) {
205 | assert.expect(3);
206 |
207 | let bb = new Backburner(['action']);
208 |
209 | let wasCalled = 0;
210 |
211 | function fn() {
212 | wasCalled++;
213 | }
214 |
215 | bb.run(() => {
216 | let timer = bb.scheduleOnce('action', fn);
217 |
218 | assert.equal(wasCalled, 0);
219 |
220 | bb.cancel(timer);
221 | bb.scheduleOnce('action', fn);
222 |
223 | assert.equal(wasCalled, 0);
224 | });
225 |
226 | assert.equal(wasCalled, 1);
227 | });
228 |
229 | QUnit.test('cancel always returns boolean', function(assert) {
230 | let bb = new Backburner(['one']);
231 |
232 | bb.run(function() {
233 | let timer1 = bb.schedule('one', null, function() {});
234 | assert.equal(bb.cancel(timer1), true);
235 | assert.equal(bb.cancel(timer1), false);
236 | assert.equal(bb.cancel(timer1), false);
237 |
238 | let timer2 = bb.later(function() {}, 10);
239 | assert.equal(bb.cancel(timer2), true);
240 | assert.equal(bb.cancel(timer2), false);
241 | assert.equal(bb.cancel(timer2), false);
242 |
243 | let timer3 = bb.debounce(function() {}, 10);
244 | assert.equal(bb.cancel(timer3), true);
245 | assert.equal(bb.cancel(timer3), false);
246 | assert.equal(bb.cancel(timer3), false);
247 |
248 | assert.equal(bb.cancel(undefined), false);
249 | assert.equal(bb.cancel(null), false);
250 | assert.equal(bb.cancel({}), false);
251 | assert.equal(bb.cancel([]), false);
252 | assert.equal(bb.cancel(42), false);
253 | assert.equal(bb.cancel('42'), false);
254 | });
255 | });
256 |
--------------------------------------------------------------------------------
/tests/autorun-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 | import lolex from 'lolex';
3 |
4 | // used to ensure tests for fake timers can reliably use native setTimeout
5 | const SET_TIMEOUT = setTimeout;
6 | let fakeClock;
7 |
8 | function escapeCurrentMicrotaskQueue() {
9 | return new Promise((resolve) => {
10 | // this ensures that we have been to the end of the current
11 | // events microtask queue
12 | setTimeout(resolve, 0);
13 | });
14 | }
15 |
16 | QUnit.module('tests/autorun', {
17 | afterEach() {
18 | if (fakeClock) {
19 | fakeClock.uninstall();
20 | }
21 | }
22 | });
23 |
24 | QUnit.test('autorun', function(assert) {
25 | let done = assert.async();
26 | let bb = new Backburner(['zomg']);
27 | let step = 0;
28 |
29 | assert.ok(!bb.currentInstance, 'The DeferredActionQueues object is lazily instaniated');
30 | assert.equal(step++, 0);
31 |
32 | bb.schedule('zomg', null, () => {
33 | assert.equal(step++, 2);
34 | setTimeout(() => {
35 | assert.ok(!bb.hasTimers(), 'The all timers are cleared');
36 | done();
37 | });
38 | });
39 |
40 | assert.ok(bb.currentInstance, 'The DeferredActionQueues object exists');
41 | assert.equal(step++, 1);
42 | });
43 |
44 | QUnit.test('autorun (joins next run if not yet flushed)', function(assert) {
45 | let bb = new Backburner(['zomg']);
46 | let order = -1;
47 |
48 | let tasks = {
49 | one: { count: 0, order: -1 },
50 | two: { count: 0, order: -1 }
51 | };
52 |
53 | bb.schedule('zomg', null, () => {
54 | tasks.one.count++;
55 | tasks.one.order = ++order;
56 | });
57 |
58 | assert.deepEqual(tasks, {
59 | one: { count: 0, order: -1 },
60 | two: { count: 0, order: -1 }
61 | });
62 |
63 | bb.run(() => {
64 | bb.schedule('zomg', null, () => {
65 | tasks.two.count++;
66 | tasks.two.order = ++order;
67 | });
68 |
69 | assert.deepEqual(tasks, {
70 | one: { count: 0, order: -1 },
71 | two: { count: 0, order: -1 }
72 | });
73 | });
74 |
75 | assert.deepEqual(tasks, {
76 | one: { count: 1, order: 0 },
77 | two: { count: 1, order: 1 }
78 | });
79 | });
80 |
81 | QUnit.test('autorun completes before items scheduled by later (via microtasks)', function(assert) {
82 | let done = assert.async();
83 | let bb = new Backburner(['first', 'second']);
84 | let order = new Array();
85 |
86 | // this later will be scheduled into the `first` queue when
87 | // its timer is up
88 | bb.later(() => {
89 | order.push('second - later');
90 | }, 0);
91 |
92 | // scheduling this into the second queue so that we can confirm this _still_
93 | // runs first (due to autorun resolving before scheduled timer)
94 | bb.schedule('second', null, () => {
95 | order.push('first - scheduled');
96 | });
97 |
98 | setTimeout(() => {
99 | assert.deepEqual(order, ['first - scheduled', 'second - later']);
100 | done();
101 | }, 20);
102 | });
103 |
104 | QUnit.test('can be canceled (private API)', function(assert) {
105 | assert.expect(0);
106 |
107 | let done = assert.async();
108 | let bb = new Backburner(['zomg']);
109 |
110 | bb.schedule('zomg', null, () => {
111 | assert.notOk(true, 'should not flush');
112 | });
113 |
114 | bb['_cancelAutorun']();
115 |
116 | setTimeout(done, 10);
117 | });
118 |
119 | QUnit.test('autorun interleaved with microtasks do not get dropped [GH#332]', function(assert) {
120 | let done = assert.async();
121 | let actual: string[] = [];
122 | let bb = new Backburner(['actions', 'render']);
123 |
124 | bb.schedule('render', function() {
125 | actual.push('first');
126 | bb.schedule('actions', () => {
127 | actual.push('action1');
128 | });
129 |
130 | Promise.resolve().then(() => {
131 | actual.push('second');
132 | bb.schedule('actions', () => {
133 | actual.push('action2');
134 | });
135 |
136 | return Promise.resolve().then(() => {
137 | actual.push('third');
138 |
139 | bb.schedule('actions', () => {
140 | actual.push('action3');
141 | });
142 | });
143 | });
144 | });
145 |
146 | setTimeout(function() {
147 | assert.deepEqual(actual, ['first', 'action1', 'second', 'action2', 'third', 'action3']);
148 |
149 | done();
150 | });
151 | });
152 |
153 | QUnit.test('autorun functions even when using fake timers', function(assert) {
154 | let done = assert.async();
155 | let bb = new Backburner(['zomg']);
156 | let step = 0;
157 |
158 | assert.ok(!bb.currentInstance, 'The DeferredActionQueues object is lazily instaniated');
159 | assert.equal(step++, 0);
160 |
161 | fakeClock = lolex.install();
162 | bb.schedule('zomg', null, () => {
163 | assert.equal(step++, 2);
164 | SET_TIMEOUT(() => {
165 | assert.ok(!bb.hasTimers(), 'The all timers are cleared');
166 | done();
167 | });
168 | });
169 |
170 | assert.ok(bb.currentInstance, 'The DeferredActionQueues object exists');
171 | assert.equal(step++, 1);
172 | });
173 |
174 | QUnit.test('customizing flushing per queue via flush', function(assert) {
175 | assert.step('start');
176 |
177 | let deferredFlush;
178 |
179 | let bb = new Backburner(
180 | [
181 | 'zomg',
182 | 'render',
183 | 'afterRender'
184 | ],
185 | {
186 | flush(queueName, flush) {
187 | if (queueName === 'render') {
188 | deferredFlush = flush;
189 | } else {
190 | flush();
191 | }
192 | }
193 | }
194 | );
195 |
196 | bb.schedule('zomg', null, () => {
197 | assert.step('running zomg');
198 | });
199 |
200 | bb.schedule('render', null, () => {
201 | assert.step('running render');
202 | });
203 |
204 | bb.schedule('afterRender', null, () => {
205 | assert.step('running afterRender');
206 | });
207 |
208 | return escapeCurrentMicrotaskQueue()
209 | .then(() => {
210 | deferredFlush();
211 | })
212 | .then(escapeCurrentMicrotaskQueue)
213 | .then(() => {
214 | assert.verifySteps([
215 | 'start',
216 | 'running zomg',
217 | 'running render',
218 | 'running afterRender',
219 | ]);
220 | });
221 | });
222 |
223 | QUnit.test('customized flushing - precedence is rechecked upon each flush', function(assert) {
224 | assert.step('start');
225 |
226 | let deferredFlush;
227 |
228 | let bb = new Backburner(
229 | [
230 | 'zomg',
231 | 'render',
232 | 'afterRender'
233 | ],
234 | {
235 | flush(queueName, flush) {
236 | if (deferredFlush === undefined && queueName === 'render') {
237 | deferredFlush = flush;
238 | } else {
239 | flush();
240 | }
241 | }
242 | }
243 | );
244 |
245 | bb.schedule('zomg', null, () => {
246 | assert.step('running zomg');
247 | });
248 |
249 | bb.schedule('render', null, () => {
250 | assert.step('running render');
251 | });
252 |
253 | bb.schedule('afterRender', null, () => {
254 | assert.step('running afterRender');
255 | });
256 |
257 | return escapeCurrentMicrotaskQueue()
258 | .then(() => {
259 | bb.schedule('zomg', null, () => {
260 | assert.step('running zomg 2');
261 | });
262 |
263 | deferredFlush();
264 | })
265 | .then(escapeCurrentMicrotaskQueue)
266 | .then(() => {
267 | assert.verifySteps([
268 | 'start',
269 | 'running zomg',
270 | 'running zomg 2',
271 | 'running render',
272 | 'running afterRender',
273 | ]);
274 | });
275 | });
276 |
277 | QUnit.test('customizing flushing per queue via flush - with forced run', function(assert) {
278 | assert.step('start');
279 |
280 | let deferredFlush;
281 |
282 | let bb = new Backburner(
283 | [
284 | 'zomg',
285 | 'render',
286 | 'afterRender'
287 | ],
288 | {
289 | flush(queueName, flush) {
290 | if (queueName === 'render') {
291 | deferredFlush = flush;
292 | } else {
293 | flush();
294 | }
295 | }
296 | }
297 | );
298 |
299 | bb.schedule('zomg', null, () => {
300 | assert.step('running zomg');
301 | });
302 |
303 | bb.schedule('render', null, () => {
304 | assert.step('running render');
305 | });
306 |
307 | bb.schedule('afterRender', null, () => {
308 | assert.step('running afterRender');
309 | });
310 |
311 | return escapeCurrentMicrotaskQueue()
312 | .then(() => {
313 | bb.run(() => {});
314 |
315 | assert.verifySteps([
316 | 'start',
317 | 'running zomg',
318 | 'running render',
319 | 'running afterRender',
320 | ]);
321 | });
322 | });
323 |
--------------------------------------------------------------------------------
/tests/defer-once-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/defer-once');
4 |
5 | QUnit.test('when passed a function', function(assert) {
6 | assert.expect(1);
7 |
8 | let bb = new Backburner(['one']);
9 | let functionWasCalled = false;
10 |
11 | bb.run(() => {
12 | bb.scheduleOnce('one', () => {
13 | functionWasCalled = true;
14 | });
15 | });
16 |
17 | assert.ok(functionWasCalled, 'function was called');
18 | });
19 |
20 | QUnit.test('when passed a target and method', function(assert) {
21 | assert.expect(2);
22 |
23 | let bb = new Backburner(['one']);
24 | let functionWasCalled = false;
25 |
26 | bb.run(() => {
27 | bb.scheduleOnce('one', {zomg: 'hi'}, function() {
28 | assert.equal(this.zomg, 'hi', 'the target was properly set');
29 | functionWasCalled = true;
30 | });
31 | });
32 |
33 | assert.ok(functionWasCalled, 'function was called');
34 | });
35 |
36 | QUnit.test('when passed a target and method name', function(assert) {
37 | assert.expect(2);
38 |
39 | let bb = new Backburner(['one']);
40 | let functionWasCalled = false;
41 | let targetObject = {
42 | zomg: 'hi',
43 | checkFunction() {
44 | assert.equal(this.zomg, 'hi', 'the target was properly set');
45 | functionWasCalled = true;
46 | }
47 | };
48 |
49 | bb.run(() => bb.scheduleOnce('one', targetObject, 'checkFunction'));
50 |
51 | assert.ok(functionWasCalled, 'function was called');
52 | });
53 |
54 | QUnit.test('throws when passed a null method', function(assert) {
55 | assert.expect(1);
56 |
57 | function onError(error) {
58 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
59 | }
60 |
61 | let bb = new Backburner(['deferErrors'], {
62 | onError
63 | });
64 |
65 | bb.run(() => bb.scheduleOnce('deferErrors', {zomg: 'hi'}, null));
66 | });
67 |
68 | QUnit.test('throws when passed an undefined method', function(assert) {
69 | assert.expect(1);
70 |
71 | function onError(error) {
72 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
73 | }
74 |
75 | let bb = new Backburner(['deferErrors'], {
76 | onError
77 | });
78 |
79 | bb.run(() => bb.deferOnce('deferErrors', {zomg: 'hi'}, undefined));
80 | });
81 |
82 | QUnit.test('throws when passed an method name that does not exists on the target', function(assert) {
83 | assert.expect(1);
84 |
85 | function onError(error) {
86 | assert.equal('You attempted to schedule an action in a queue (deferErrors) for a method that doesn\'t exist', error.message);
87 | }
88 |
89 | let bb = new Backburner(['deferErrors'], {
90 | onError
91 | });
92 |
93 | bb.run(() => bb.deferOnce('deferErrors', {zomg: 'hi'}, 'checkFunction'));
94 | });
95 |
96 | QUnit.test('when passed a target, method, and arguments', function(assert) {
97 | assert.expect(5);
98 |
99 | let bb = new Backburner(['one']);
100 | let functionWasCalled = false;
101 |
102 | bb.run(() => {
103 | bb.scheduleOnce('one', {zomg: 'hi'}, function(a, b, c) {
104 | assert.equal(this.zomg, 'hi', 'the target was properly set');
105 | assert.equal(a, 1, 'the first arguments was passed in');
106 | assert.equal(b, 2, 'the second arguments was passed in');
107 | assert.equal(c, 3, 'the third arguments was passed in');
108 | functionWasCalled = true;
109 | }, 1, 2, 3);
110 | });
111 |
112 | assert.ok(functionWasCalled, 'function was called');
113 | });
114 |
115 | QUnit.test('when passed same function twice', function(assert) {
116 | assert.expect(2);
117 |
118 | let bb = new Backburner(['one']);
119 | let i = 0;
120 | let functionWasCalled = false;
121 |
122 | function deferMethod() {
123 | i++;
124 | assert.equal(i, 1, 'Function should be called only once');
125 | functionWasCalled = true;
126 | }
127 |
128 | bb.run(() => {
129 | bb.scheduleOnce('one', deferMethod);
130 | bb.scheduleOnce('one', deferMethod);
131 | });
132 |
133 | assert.ok(functionWasCalled, 'function was called only once');
134 | });
135 |
136 | QUnit.test('when passed same function twice with same target', function(assert) {
137 | assert.expect(3);
138 |
139 | let bb = new Backburner(['one']);
140 | let i = 0;
141 | let functionWasCalled = false;
142 |
143 | function deferMethod() {
144 | i++;
145 | assert.equal(i, 1, 'Function should be called only once');
146 | assert.equal(this['first'], 1, 'the target property was set');
147 | functionWasCalled = true;
148 | }
149 |
150 | let argObj = {first: 1};
151 |
152 | bb.run(() => {
153 | bb.scheduleOnce('one', argObj, deferMethod);
154 | bb.scheduleOnce('one', argObj, deferMethod);
155 | });
156 |
157 | assert.ok(functionWasCalled, 'function was called only once');
158 | });
159 |
160 | QUnit.test('when passed same function twice with different targets', function(assert) {
161 | assert.expect(3);
162 |
163 | let bb = new Backburner(['one']);
164 | let i = 0;
165 |
166 | function deferMethod() {
167 | i++;
168 | assert.equal(this['first'], 1, 'the target property was set');
169 | }
170 |
171 | bb.run(() => {
172 | bb.scheduleOnce('one', {first: 1}, deferMethod);
173 | bb.scheduleOnce('one', {first: 1}, deferMethod);
174 | });
175 |
176 | assert.equal(i, 2, 'function was called twice');
177 | });
178 |
179 | QUnit.test('when passed same function twice with same arguments and same target', function(assert) {
180 | assert.expect(4);
181 |
182 | let bb = new Backburner(['one']);
183 | let i = 0;
184 |
185 | function deferMethod(a, b) {
186 | i++;
187 | assert.equal(a, 1, 'First argument is set only one time');
188 | assert.equal(b, 2, 'Second argument remains same');
189 | assert.equal(this['first'], 1, 'the target property was set');
190 | }
191 |
192 | let argObj = {first: 1};
193 |
194 | bb.run(() => {
195 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2);
196 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2);
197 | });
198 |
199 | assert.equal(i, 1, 'function was called once');
200 | });
201 |
202 | QUnit.test('when passed same function twice with same target and different arguments', function(assert) {
203 | assert.expect(4);
204 |
205 | let bb = new Backburner(['one']);
206 | let i = 0;
207 |
208 | function deferMethod(a, b) {
209 | i++;
210 | assert.equal(a, 3, 'First argument of only second call is set');
211 | assert.equal(b, 2, 'Second argument remains same');
212 | assert.equal(this['first'], 1, 'the target property was set');
213 | }
214 |
215 | let argObj = {first: 1};
216 |
217 | bb.run(() => {
218 | bb.scheduleOnce('one', argObj, deferMethod, 1, 2);
219 | bb.scheduleOnce('one', argObj, deferMethod, 3, 2);
220 | });
221 |
222 | assert.equal(i, 1, 'function was called once');
223 | });
224 |
225 | QUnit.test('when passed same function twice with different target and different arguments', function(assert) {
226 | assert.expect(7);
227 |
228 | let bb = new Backburner(['one']);
229 | let i = 0;
230 |
231 | function deferMethod(a, b) {
232 | i++;
233 | if (i === 1) {
234 | assert.equal(a, 1, 'First argument set during first call');
235 | } else {
236 | assert.equal(a, 3, 'First argument set during second call');
237 | }
238 | assert.equal(b, 2, 'Second argument remains same');
239 | assert.equal(this['first'], 1, 'the target property was set');
240 | }
241 |
242 | let argObj = {first: 1};
243 |
244 | bb.run(() => {
245 | bb.scheduleOnce('one', {first: 1}, deferMethod, 1, 2);
246 | bb.scheduleOnce('one', {first: 1}, deferMethod, 3, 2);
247 | });
248 |
249 | assert.equal(i, 2, 'function was called twice');
250 | });
251 |
252 | QUnit.test('when passed same function with same target after already triggering in current loop', function(assert) {
253 | assert.expect(5);
254 |
255 | let bb = new Backburner(['one', 'two']);
256 | let i = 0;
257 |
258 | function deferMethod(a) {
259 | i++;
260 | assert.equal(a, i, 'Correct argument is set');
261 | assert.equal(this['first'], 1, 'the target property was set');
262 | }
263 |
264 | function scheduleMethod() {
265 | bb.scheduleOnce('one', argObj, deferMethod, 2);
266 | }
267 |
268 | let argObj = {first: 1};
269 |
270 | bb.run(() => {
271 | bb.scheduleOnce('one', argObj, deferMethod, 1);
272 | bb.scheduleOnce('two', argObj, scheduleMethod);
273 | });
274 |
275 | assert.equal(i, 2, 'function was called twice');
276 | });
277 |
278 | QUnit.test('when passed same function with same target after already triggering in current loop', function(assert) {
279 | assert.expect(5);
280 |
281 | let argObj = {first: 1};
282 | let bb = new Backburner(['one', 'two'], { });
283 |
284 | let i = 0;
285 |
286 | function deferMethod(a) {
287 | i++;
288 | assert.equal(a, i, 'Correct argument is set');
289 | assert.equal(this['first'], 1, 'the target property was set');
290 | }
291 |
292 | function scheduleMethod() {
293 | bb.scheduleOnce('one', argObj, deferMethod, 2);
294 | }
295 |
296 | bb.run(() => {
297 | bb.scheduleOnce('one', argObj, deferMethod, 1);
298 | bb.scheduleOnce('two', argObj, scheduleMethod);
299 | });
300 |
301 | assert.equal(i, 2, 'function was called twice');
302 | });
303 |
304 | QUnit.test('onError', function(assert) {
305 | assert.expect(1);
306 |
307 | function onError(error) {
308 | assert.equal('QUnit.test error', error.message);
309 | }
310 |
311 | let bb = new Backburner(['errors'], { onError });
312 |
313 | bb.run(() => {
314 | bb.scheduleOnce('errors', () => {
315 | throw new Error('QUnit.test error');
316 | });
317 | });
318 | });
319 |
320 | QUnit.test('when [queueName, callback, string] args passed', function(assert) {
321 | assert.expect(2);
322 |
323 | let bb = new Backburner(['one']);
324 | let functionWasCalled = false;
325 |
326 | bb.run(() => {
327 | bb.scheduleOnce('one', function(name) {
328 | assert.equal(name, 'batman');
329 | functionWasCalled = true;
330 | }, 'batman', 100);
331 | });
332 |
333 | assert.ok(functionWasCalled, 'function was called');
334 | });
335 |
--------------------------------------------------------------------------------
/dist/loader.js:
--------------------------------------------------------------------------------
1 | var loader, define, requireModule, require, requirejs;
2 |
3 | (function (global) {
4 | 'use strict';
5 |
6 | function dict() {
7 | var obj = Object.create(null);
8 | obj['__'] = undefined;
9 | delete obj['__'];
10 | return obj;
11 | }
12 |
13 | // Save off the original values of these globals, so we can restore them if someone asks us to
14 | var oldGlobals = {
15 | loader: loader,
16 | define: define,
17 | requireModule: requireModule,
18 | require: require,
19 | requirejs: requirejs
20 | };
21 |
22 | requirejs = require = requireModule = function (id) {
23 | var pending = [];
24 | var mod = findModule(id, '(require)', pending);
25 |
26 | for (var i = pending.length - 1; i >= 0; i--) {
27 | pending[i].exports();
28 | }
29 |
30 | return mod.module.exports;
31 | };
32 |
33 | loader = {
34 | noConflict: function (aliases) {
35 | var oldName, newName;
36 |
37 | for (oldName in aliases) {
38 | if (aliases.hasOwnProperty(oldName)) {
39 | if (oldGlobals.hasOwnProperty(oldName)) {
40 | newName = aliases[oldName];
41 |
42 | global[newName] = global[oldName];
43 | global[oldName] = oldGlobals[oldName];
44 | }
45 | }
46 | }
47 | },
48 | // Option to enable or disable the generation of default exports
49 | makeDefaultExport: true
50 | };
51 |
52 | var registry = dict();
53 | var seen = dict();
54 |
55 | var uuid = 0;
56 |
57 | function unsupportedModule(length) {
58 | throw new Error('an unsupported module was defined, expected `define(id, deps, module)` instead got: `' + length + '` arguments to define`');
59 | }
60 |
61 | var defaultDeps = ['require', 'exports', 'module'];
62 |
63 | function Module(id, deps, callback, alias) {
64 | this.uuid = uuid++;
65 | this.id = id;
66 | this.deps = !deps.length && callback.length ? defaultDeps : deps;
67 | this.module = { exports: {} };
68 | this.callback = callback;
69 | this.hasExportsAsDep = false;
70 | this.isAlias = alias;
71 | this.reified = new Array(deps.length);
72 |
73 | /*
74 | Each module normally passes through these states, in order:
75 | new : initial state
76 | pending : this module is scheduled to be executed
77 | reifying : this module's dependencies are being executed
78 | reified : this module's dependencies finished executing successfully
79 | errored : this module's dependencies failed to execute
80 | finalized : this module executed successfully
81 | */
82 | this.state = 'new';
83 | }
84 |
85 | Module.prototype.makeDefaultExport = function () {
86 | var exports = this.module.exports;
87 | if (exports !== null && (typeof exports === 'object' || typeof exports === 'function') && exports['default'] === undefined && Object.isExtensible(exports)) {
88 | exports['default'] = exports;
89 | }
90 | };
91 |
92 | Module.prototype.exports = function () {
93 | // if finalized, there is no work to do. If reifying, there is a
94 | // circular dependency so we must return our (partial) exports.
95 | if (this.state === 'finalized' || this.state === 'reifying') {
96 | return this.module.exports;
97 | }
98 |
99 |
100 | if (loader.wrapModules) {
101 | this.callback = loader.wrapModules(this.id, this.callback);
102 | }
103 |
104 | this.reify();
105 |
106 | var result = this.callback.apply(this, this.reified);
107 | this.reified.length = 0;
108 | this.state = 'finalized';
109 |
110 | if (!(this.hasExportsAsDep && result === undefined)) {
111 | this.module.exports = result;
112 | }
113 | if (loader.makeDefaultExport) {
114 | this.makeDefaultExport();
115 | }
116 | return this.module.exports;
117 | };
118 |
119 | Module.prototype.unsee = function () {
120 | this.state = 'new';
121 | this.module = { exports: {} };
122 | };
123 |
124 | Module.prototype.reify = function () {
125 | if (this.state === 'reified') {
126 | return;
127 | }
128 | this.state = 'reifying';
129 | try {
130 | this.reified = this._reify();
131 | this.state = 'reified';
132 | } finally {
133 | if (this.state === 'reifying') {
134 | this.state = 'errored';
135 | }
136 | }
137 | };
138 |
139 | Module.prototype._reify = function () {
140 | var reified = this.reified.slice();
141 | for (var i = 0; i < reified.length; i++) {
142 | var mod = reified[i];
143 | reified[i] = mod.exports ? mod.exports : mod.module.exports();
144 | }
145 | return reified;
146 | };
147 |
148 | Module.prototype.findDeps = function (pending) {
149 | if (this.state !== 'new') {
150 | return;
151 | }
152 |
153 | this.state = 'pending';
154 |
155 | var deps = this.deps;
156 |
157 | for (var i = 0; i < deps.length; i++) {
158 | var dep = deps[i];
159 | var entry = this.reified[i] = { exports: undefined, module: undefined };
160 | if (dep === 'exports') {
161 | this.hasExportsAsDep = true;
162 | entry.exports = this.module.exports;
163 | } else if (dep === 'require') {
164 | entry.exports = this.makeRequire();
165 | } else if (dep === 'module') {
166 | entry.exports = this.module;
167 | } else {
168 | entry.module = findModule(resolve(dep, this.id), this.id, pending);
169 | }
170 | }
171 | };
172 |
173 | Module.prototype.makeRequire = function () {
174 | var id = this.id;
175 | var r = function (dep) {
176 | return require(resolve(dep, id));
177 | };
178 | r['default'] = r;
179 | r.moduleId = id;
180 | r.has = function (dep) {
181 | return has(resolve(dep, id));
182 | };
183 | return r;
184 | };
185 |
186 | define = function (id, deps, callback) {
187 | var module = registry[id];
188 |
189 | // If a module for this id has already been defined and is in any state
190 | // other than `new` (meaning it has been or is currently being required),
191 | // then we return early to avoid redefinition.
192 | if (module && module.state !== 'new') {
193 | return;
194 | }
195 |
196 | if (arguments.length < 2) {
197 | unsupportedModule(arguments.length);
198 | }
199 |
200 | if (!Array.isArray(deps)) {
201 | callback = deps;
202 | deps = [];
203 | }
204 |
205 | if (callback instanceof Alias) {
206 | registry[id] = new Module(callback.id, deps, callback, true);
207 | } else {
208 | registry[id] = new Module(id, deps, callback, false);
209 | }
210 | };
211 |
212 | define.exports = function (name, defaultExport) {
213 | var module = registry[name];
214 |
215 | // If a module for this name has already been defined and is in any state
216 | // other than `new` (meaning it has been or is currently being required),
217 | // then we return early to avoid redefinition.
218 | if (module && module.state !== 'new') {
219 | return;
220 | }
221 |
222 | module = new Module(name, [], noop, null);
223 | module.module.exports = defaultExport;
224 | module.state = 'finalized';
225 | registry[name] = module;
226 |
227 | return module;
228 | };
229 |
230 | function noop() {}
231 | // we don't support all of AMD
232 | // define.amd = {};
233 |
234 | function Alias(id) {
235 | this.id = id;
236 | }
237 |
238 | define.alias = function (id, target) {
239 | if (arguments.length === 2) {
240 | return define(target, new Alias(id));
241 | }
242 |
243 | return new Alias(id);
244 | };
245 |
246 | function missingModule(id, referrer) {
247 | throw new Error('Could not find module `' + id + '` imported from `' + referrer + '`');
248 | }
249 |
250 | function findModule(id, referrer, pending) {
251 | var mod = registry[id] || registry[id + '/index'];
252 |
253 | while (mod && mod.isAlias) {
254 | mod = registry[mod.id] || registry[mod.id + '/index'];
255 | }
256 |
257 | if (!mod) {
258 | missingModule(id, referrer);
259 | }
260 |
261 | if (pending && mod.state !== 'pending' && mod.state !== 'finalized') {
262 | mod.findDeps(pending);
263 | pending.push(mod);
264 | }
265 | return mod;
266 | }
267 |
268 | function resolve(child, id) {
269 | if (child.charAt(0) !== '.') {
270 | return child;
271 | }
272 |
273 |
274 | var parts = child.split('/');
275 | var nameParts = id.split('/');
276 | var parentBase = nameParts.slice(0, -1);
277 |
278 | for (var i = 0, l = parts.length; i < l; i++) {
279 | var part = parts[i];
280 |
281 | if (part === '..') {
282 | if (parentBase.length === 0) {
283 | throw new Error('Cannot access parent module of root');
284 | }
285 | parentBase.pop();
286 | } else if (part === '.') {
287 | continue;
288 | } else {
289 | parentBase.push(part);
290 | }
291 | }
292 |
293 | return parentBase.join('/');
294 | }
295 |
296 | function has(id) {
297 | return !!(registry[id] || registry[id + '/index']);
298 | }
299 |
300 | requirejs.entries = requirejs._eak_seen = registry;
301 | requirejs.has = has;
302 | requirejs.unsee = function (id) {
303 | findModule(id, '(unsee)', false).unsee();
304 | };
305 |
306 | requirejs.clear = function () {
307 | requirejs.entries = requirejs._eak_seen = registry = dict();
308 | seen = dict();
309 | };
310 |
311 | // This code primes the JS engine for good performance by warming the
312 | // JIT compiler for these functions.
313 | define('foo', function () {});
314 | define('foo/bar', [], function () {});
315 | define('foo/asdf', ['module', 'exports', 'require'], function (module, exports, require) {
316 | if (require.has('foo/bar')) {
317 | require('foo/bar');
318 | }
319 | });
320 | define('foo/baz', [], define.alias('foo'));
321 | define('foo/quz', define.alias('foo'));
322 | define.alias('foo', 'foo/qux');
323 | define('foo/bar', ['foo', './quz', './baz', './asdf', './bar', '../foo'], function () {});
324 | define('foo/main', ['foo/bar'], function () {});
325 | define.exports('foo/exports', {});
326 |
327 | require('foo/exports');
328 | require('foo/main');
329 | require.unsee('foo/bar');
330 |
331 | requirejs.clear();
332 |
333 | if (typeof exports === 'object' && typeof module === 'object' && module.exports) {
334 | module.exports = { require: require, define: define };
335 | }
336 | })(this);
--------------------------------------------------------------------------------
/dist/tests/qunit.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * QUnit 2.15.0
3 | * https://qunitjs.com/
4 | *
5 | * Copyright OpenJS Foundation and other contributors
6 | * Released under the MIT license
7 | * https://jquery.org/license
8 | */
9 |
10 | /** Font Family and Sizes */
11 |
12 | /* Style our buttons in a simple way, uninfluenced by the styles
13 | the tested app might load. Don't affect buttons in #qunit-fixture!
14 | https://github.com/qunitjs/qunit/pull/1395
15 | https://github.com/qunitjs/qunit/issues/1437 */
16 | #qunit-testrunner-toolbar button,
17 | #qunit-testresult button {
18 | font-size: initial;
19 | border: initial;
20 | background-color: buttonface;
21 | }
22 |
23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
24 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
25 | }
26 |
27 | #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
28 | #qunit-tests { font-size: smaller; }
29 |
30 |
31 | /** Resets */
32 |
33 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
34 | margin: 0;
35 | padding: 0;
36 | }
37 |
38 |
39 | /** Fixed headers with scrollable tests */
40 |
41 | @supports (display: flex) or (display: -webkit-box) {
42 | @media (min-height: 500px) {
43 | #qunit {
44 | position: fixed;
45 | left: 0px;
46 | right: 0px;
47 | top: 0px;
48 | bottom: 0px;
49 | padding: 8px;
50 | display: -webkit-box;
51 | display: flex;
52 | flex-direction: column;
53 | }
54 |
55 | #qunit-tests {
56 | overflow: scroll;
57 | }
58 |
59 | #qunit-banner {
60 | flex: 5px 0 0;
61 | }
62 | }
63 | }
64 |
65 |
66 | /** Header (excluding toolbar) */
67 |
68 | #qunit-header {
69 | padding: 0.5em 0 0.5em 1em;
70 |
71 | color: #8699A4;
72 | background-color: #0D3349;
73 |
74 | font-size: 1.5em;
75 | line-height: 1em;
76 | font-weight: 400;
77 |
78 | border-radius: 5px 5px 0 0;
79 | }
80 |
81 | #qunit-header a {
82 | text-decoration: none;
83 | color: #C2CCD1;
84 | }
85 |
86 | #qunit-header a:hover,
87 | #qunit-header a:focus {
88 | color: #FFF;
89 | }
90 |
91 | #qunit-banner {
92 | height: 5px;
93 | }
94 |
95 | #qunit-filteredTest {
96 | padding: 0.5em 1em 0.5em 1em;
97 | color: #366097;
98 | background-color: #F4FF77;
99 | }
100 |
101 | #qunit-userAgent {
102 | padding: 0.5em 1em 0.5em 1em;
103 | color: #FFF;
104 | background-color: #2B81AF;
105 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
106 | }
107 |
108 |
109 | /** Toolbar */
110 |
111 | #qunit-testrunner-toolbar {
112 | padding: 0.5em 1em 0.5em 1em;
113 | color: #5E740B;
114 | background-color: #EEE;
115 | }
116 |
117 | #qunit-testrunner-toolbar .clearfix {
118 | height: 0;
119 | clear: both;
120 | }
121 |
122 | #qunit-testrunner-toolbar label {
123 | display: inline-block;
124 | }
125 |
126 | #qunit-testrunner-toolbar input[type=checkbox],
127 | #qunit-testrunner-toolbar input[type=radio] {
128 | margin: 3px;
129 | vertical-align: -2px;
130 | }
131 |
132 | #qunit-testrunner-toolbar input[type=text] {
133 | box-sizing: border-box;
134 | height: 1.6em;
135 | }
136 |
137 | #qunit-toolbar-filters {
138 | float: right;
139 | }
140 |
141 | .qunit-url-config,
142 | .qunit-filter,
143 | #qunit-modulefilter {
144 | display: inline-block;
145 | line-height: 2.1em;
146 | }
147 |
148 | .qunit-filter,
149 | #qunit-modulefilter {
150 | position: relative;
151 | margin-left: 1em;
152 | }
153 |
154 | .qunit-url-config label {
155 | margin-right: 0.5em;
156 | }
157 |
158 | #qunit-modulefilter-search {
159 | box-sizing: border-box;
160 | min-width: 400px;
161 | }
162 |
163 | #qunit-modulefilter-search-container:after {
164 | position: absolute;
165 | right: 0.3em;
166 | content: "\25bc";
167 | color: black;
168 | }
169 |
170 | #qunit-modulefilter-dropdown {
171 | /* align with #qunit-modulefilter-search */
172 | box-sizing: border-box;
173 | min-width: 400px;
174 | position: absolute;
175 | right: 0;
176 | top: 50%;
177 | margin-top: 0.8em;
178 |
179 | border: 1px solid #D3D3D3;
180 | border-top: none;
181 | border-radius: 0 0 .25em .25em;
182 | color: #000;
183 | background-color: #F5F5F5;
184 | z-index: 99;
185 | }
186 |
187 | #qunit-modulefilter-dropdown a {
188 | color: inherit;
189 | text-decoration: none;
190 | }
191 |
192 | #qunit-modulefilter-dropdown .clickable.checked {
193 | font-weight: bold;
194 | color: #000;
195 | background-color: #D2E0E6;
196 | }
197 |
198 | #qunit-modulefilter-dropdown .clickable:hover {
199 | color: #FFF;
200 | background-color: #0D3349;
201 | }
202 |
203 | #qunit-modulefilter-actions {
204 | display: block;
205 | overflow: auto;
206 |
207 | /* align with #qunit-modulefilter-dropdown-list */
208 | font: smaller/1.5em sans-serif;
209 | }
210 |
211 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
212 | box-sizing: border-box;
213 | max-height: 2.8em;
214 | display: block;
215 | padding: 0.4em;
216 | }
217 |
218 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
219 | float: right;
220 | font: inherit;
221 | }
222 |
223 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
224 | /* insert padding to align with checkbox margins */
225 | padding-left: 3px;
226 | }
227 |
228 | #qunit-modulefilter-dropdown-list {
229 | max-height: 200px;
230 | overflow-y: auto;
231 | margin: 0;
232 | border-top: 2px groove threedhighlight;
233 | padding: 0.4em 0 0;
234 | font: smaller/1.5em sans-serif;
235 | }
236 |
237 | #qunit-modulefilter-dropdown-list li {
238 | white-space: nowrap;
239 | overflow: hidden;
240 | text-overflow: ellipsis;
241 | }
242 |
243 | #qunit-modulefilter-dropdown-list .clickable {
244 | display: block;
245 | padding-left: 0.15em;
246 | padding-right: 0.5em;
247 | }
248 |
249 |
250 | /** Tests: Pass/Fail */
251 |
252 | #qunit-tests {
253 | list-style-position: inside;
254 | }
255 |
256 | #qunit-tests li {
257 | padding: 0.4em 1em 0.4em 1em;
258 | border-bottom: 1px solid #FFF;
259 | list-style-position: inside;
260 | }
261 |
262 | #qunit-tests > li {
263 | display: none;
264 | }
265 |
266 | #qunit-tests li.running,
267 | #qunit-tests li.pass,
268 | #qunit-tests li.fail,
269 | #qunit-tests li.skipped,
270 | #qunit-tests li.aborted {
271 | display: list-item;
272 | }
273 |
274 | #qunit-tests.hidepass {
275 | position: relative;
276 | }
277 |
278 | #qunit-tests.hidepass li.running,
279 | #qunit-tests.hidepass li.pass:not(.todo) {
280 | visibility: hidden;
281 | position: absolute;
282 | width: 0;
283 | height: 0;
284 | padding: 0;
285 | border: 0;
286 | margin: 0;
287 | }
288 |
289 | #qunit-tests li strong {
290 | cursor: pointer;
291 | }
292 |
293 | #qunit-tests li.skipped strong {
294 | cursor: default;
295 | }
296 |
297 | #qunit-tests li a {
298 | padding: 0.5em;
299 | color: #C2CCD1;
300 | text-decoration: none;
301 | }
302 |
303 | #qunit-tests li p a {
304 | padding: 0.25em;
305 | color: #6B6464;
306 | }
307 | #qunit-tests li a:hover,
308 | #qunit-tests li a:focus {
309 | color: #000;
310 | }
311 |
312 | #qunit-tests li .runtime {
313 | float: right;
314 | font-size: smaller;
315 | }
316 |
317 | .qunit-assert-list {
318 | margin-top: 0.5em;
319 | padding: 0.5em;
320 |
321 | background-color: #FFF;
322 |
323 | border-radius: 5px;
324 | }
325 |
326 | .qunit-source {
327 | margin: 0.6em 0 0.3em;
328 | }
329 |
330 | .qunit-collapsed {
331 | display: none;
332 | }
333 |
334 | #qunit-tests table {
335 | border-collapse: collapse;
336 | margin-top: 0.2em;
337 | }
338 |
339 | #qunit-tests th {
340 | text-align: right;
341 | vertical-align: top;
342 | padding: 0 0.5em 0 0;
343 | }
344 |
345 | #qunit-tests td {
346 | vertical-align: top;
347 | }
348 |
349 | #qunit-tests pre {
350 | margin: 0;
351 | white-space: pre-wrap;
352 | word-wrap: break-word;
353 | }
354 |
355 | #qunit-tests del {
356 | color: #374E0C;
357 | background-color: #E0F2BE;
358 | text-decoration: none;
359 | }
360 |
361 | #qunit-tests ins {
362 | color: #500;
363 | background-color: #FFCACA;
364 | text-decoration: none;
365 | }
366 |
367 | /*** Test Counts */
368 |
369 | #qunit-tests b.counts { color: #000; }
370 | #qunit-tests b.passed { color: #5E740B; }
371 | #qunit-tests b.failed { color: #710909; }
372 |
373 | #qunit-tests li li {
374 | padding: 5px;
375 | background-color: #FFF;
376 | border-bottom: none;
377 | list-style-position: inside;
378 | }
379 |
380 | /*** Passing Styles */
381 |
382 | #qunit-tests li li.pass {
383 | color: #3C510C;
384 | background-color: #FFF;
385 | border-left: 10px solid #C6E746;
386 | }
387 |
388 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
389 | #qunit-tests .pass .test-name { color: #366097; }
390 |
391 | #qunit-tests .pass .test-actual,
392 | #qunit-tests .pass .test-expected { color: #999; }
393 |
394 | #qunit-banner.qunit-pass { background-color: #C6E746; }
395 |
396 | /*** Failing Styles */
397 |
398 | #qunit-tests li li.fail {
399 | color: #710909;
400 | background-color: #FFF;
401 | border-left: 10px solid #EE5757;
402 | white-space: pre;
403 | }
404 |
405 | #qunit-tests > li:last-child {
406 | border-radius: 0 0 5px 5px;
407 | }
408 |
409 | #qunit-tests .fail { color: #000; background-color: #EE5757; }
410 | #qunit-tests .fail .test-name,
411 | #qunit-tests .fail .module-name { color: #000; }
412 |
413 | #qunit-tests .fail .test-actual { color: #EE5757; }
414 | #qunit-tests .fail .test-expected { color: #008000; }
415 |
416 | #qunit-banner.qunit-fail { background-color: #EE5757; }
417 |
418 |
419 | /*** Aborted tests */
420 | #qunit-tests .aborted { color: #000; background-color: orange; }
421 | /*** Skipped tests */
422 |
423 | #qunit-tests .skipped {
424 | background-color: #EBECE9;
425 | }
426 |
427 | #qunit-tests .qunit-todo-label,
428 | #qunit-tests .qunit-skipped-label {
429 | background-color: #F4FF77;
430 | display: inline-block;
431 | font-style: normal;
432 | color: #366097;
433 | line-height: 1.8em;
434 | padding: 0 0.5em;
435 | margin: -0.4em 0.4em -0.4em 0;
436 | }
437 |
438 | #qunit-tests .qunit-todo-label {
439 | background-color: #EEE;
440 | }
441 |
442 | /** Result */
443 |
444 | #qunit-testresult {
445 | color: #2B81AF;
446 | background-color: #D2E0E6;
447 |
448 | border-bottom: 1px solid #FFF;
449 | }
450 | #qunit-testresult .clearfix {
451 | height: 0;
452 | clear: both;
453 | }
454 | #qunit-testresult .module-name {
455 | font-weight: 700;
456 | }
457 | #qunit-testresult-display {
458 | padding: 0.5em 1em 0.5em 1em;
459 | width: 85%;
460 | float:left;
461 | }
462 | #qunit-testresult-controls {
463 | padding: 0.5em 1em 0.5em 1em;
464 | width: 10%;
465 | float:left;
466 | }
467 |
468 | /** Fixture */
469 |
470 | #qunit-fixture {
471 | position: absolute;
472 | top: -10000px;
473 | left: -10000px;
474 | width: 1000px;
475 | height: 1000px;
476 | }
477 |
--------------------------------------------------------------------------------
/dist/backburner.d.ts:
--------------------------------------------------------------------------------
1 | export { buildPlatform, IPlatform } from './backburner/platform';
2 | import { buildNext, buildPlatform, IPlatform } from './backburner/platform';
3 | import DeferredActionQueues from './backburner/deferred-action-queues';
4 | export type { DeferredActionQueues };
5 | import { Iterable } from './backburner/iterator-drain';
6 | import Queue from './backburner/queue';
7 | export declare type Timer = string | number;
8 | export interface IBackburnerOptions {
9 | defaultQueue?: string;
10 | onBegin?: (currentInstance: DeferredActionQueues, previousInstance: DeferredActionQueues) => void;
11 | onEnd?: (currentInstance: DeferredActionQueues, nextInstance: DeferredActionQueues) => void;
12 | onError?: (error: any, errorRecordedForStack?: any) => void;
13 | onErrorTarget?: any;
14 | onErrorMethod?: string;
15 | mustYield?: () => boolean;
16 | _buildPlatform?: (flush: () => void) => IPlatform;
17 | flush?(queueName: string, flush: () => void): void;
18 | }
19 | export default class Backburner {
20 | static Queue: typeof Queue;
21 | static buildPlatform: typeof buildPlatform;
22 | static buildNext: typeof buildNext;
23 | DEBUG: boolean;
24 | ASYNC_STACKS: boolean;
25 | currentInstance: DeferredActionQueues | null;
26 | options: IBackburnerOptions;
27 | get counters(): {
28 | begin: number;
29 | end: number;
30 | events: {
31 | begin: number;
32 | end: number;
33 | };
34 | autoruns: {
35 | created: number;
36 | completed: number;
37 | };
38 | run: number;
39 | join: number;
40 | defer: number;
41 | schedule: number;
42 | scheduleIterable: number;
43 | deferOnce: number;
44 | scheduleOnce: number;
45 | setTimeout: number;
46 | later: number;
47 | throttle: number;
48 | debounce: number;
49 | cancelTimers: number;
50 | cancel: number;
51 | loops: {
52 | total: number;
53 | nested: number;
54 | };
55 | };
56 | private _onBegin;
57 | private _onEnd;
58 | private queueNames;
59 | private instanceStack;
60 | private _eventCallbacks;
61 | private _timerTimeoutId;
62 | private _timers;
63 | private _platform;
64 | private _boundRunExpiredTimers;
65 | private _autorun;
66 | private _autorunStack;
67 | private _boundAutorunEnd;
68 | private _defaultQueue;
69 | constructor(queueNames: string[], options?: IBackburnerOptions);
70 | get defaultQueue(): string;
71 | begin(): DeferredActionQueues;
72 | end(): void;
73 | on(eventName: any, callback: any): void;
74 | off(eventName: any, callback: any): void;
75 | run(target: Function): any;
76 | run(target: Function | any | null, method?: Function | string, ...args: any[]): any;
77 | run(target: any | null | undefined, method?: Function, ...args: any[]): any;
78 | join(target: Function): any;
79 | join(target: Function | any | null, method?: Function | string, ...args: any[]): any;
80 | join(target: any | null | undefined, method?: Function, ...args: any[]): any;
81 | /**
82 | * @deprecated please use schedule instead.
83 | */
84 | defer(queueName: any, target: any, method: any, ...args: any[]): any;
85 | /**
86 | * Schedule the passed function to run inside the specified queue.
87 | */
88 | schedule(queueName: string, method: Function): any;
89 | schedule(queueName: string, target: T, method: U, ...args: any[]): any;
90 | schedule(queueName: string, target: any, method: any | Function, ...args: any[]): any;
91 | scheduleIterable(queueName: string, iterable: () => Iterable): {
92 | queue: Queue;
93 | target: any;
94 | method: any;
95 | };
96 | /**
97 | * @deprecated please use scheduleOnce instead.
98 | */
99 | deferOnce(queueName: any, target: any, method: any, ...args: any[]): any;
100 | /**
101 | * Schedule the passed function to run once inside the specified queue.
102 | */
103 | scheduleOnce(queueName: string, method: Function): any;
104 | scheduleOnce(queueName: string, target: T, method: U, ...args: any[]): any;
105 | scheduleOnce(queueName: string, target: any | null, method: any | Function, ...args: any[]): any;
106 | /**
107 | * @deprecated use later instead.
108 | */
109 | setTimeout(...args: any[]): any;
110 | later(...args: any[]): Timer;
111 | later(target: T, methodName: keyof T, wait?: number | string): Timer;
112 | later(target: T, methodName: keyof T, arg1: any, wait?: number | string): Timer;
113 | later(target: T, methodName: keyof T, arg1: any, arg2: any, wait?: number | string): Timer;
114 | later(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait?: number | string): Timer;
115 | later(thisArg: any | null, method: () => void, wait?: number | string): Timer;
116 | later(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait?: number | string): Timer;
117 | later(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string): Timer;
118 | later(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string): Timer;
119 | later(method: () => void, wait?: number | string): Timer;
120 | later(method: (arg1: A) => void, arg1: A, wait?: number | string): Timer;
121 | later(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string): Timer;
122 | later(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string): Timer;
123 | throttle(target: T, methodName: keyof T, wait?: number | string, immediate?: boolean): Timer;
124 | throttle(target: T, methodName: keyof T, arg1: any, wait?: number | string, immediate?: boolean): Timer;
125 | throttle(target: T, methodName: keyof T, arg1: any, arg2: any, wait?: number | string, immediate?: boolean): Timer;
126 | throttle(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait?: number | string, immediate?: boolean): Timer;
127 | throttle(thisArg: any | null, method: () => void, wait?: number | string, immediate?: boolean): Timer;
128 | throttle(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait?: number | string, immediate?: boolean): Timer;
129 | throttle(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string, immediate?: boolean): Timer;
130 | throttle(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string, immediate?: boolean): Timer;
131 | throttle(method: () => void, wait?: number | string, immediate?: boolean): Timer;
132 | throttle(method: (arg1: A) => void, arg1: A, wait?: number | string, immediate?: boolean): Timer;
133 | throttle(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait?: number | string, immediate?: boolean): Timer;
134 | throttle(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait?: number | string, immediate?: boolean): Timer;
135 | debounce(target: T, methodName: keyof T, wait: number | string, immediate?: boolean): Timer;
136 | debounce(target: T, methodName: keyof T, arg1: any, wait: number | string, immediate?: boolean): Timer;
137 | debounce(target: T, methodName: keyof T, arg1: any, arg2: any, wait: number | string, immediate?: boolean): Timer;
138 | debounce(target: T, methodName: keyof T, arg1: any, arg2: any, arg3: any, wait: number | string, immediate?: boolean): Timer;
139 | debounce(thisArg: any | null, method: () => void, wait: number | string, immediate?: boolean): Timer;
140 | debounce(thisArg: any | null, method: (arg1: A) => void, arg1: A, wait: number | string, immediate?: boolean): Timer;
141 | debounce(thisArg: any | null, method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait: number | string, immediate?: boolean): Timer;
142 | debounce(thisArg: any | null, method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait: number | string, immediate?: boolean): Timer;
143 | debounce(method: () => void, wait: number | string, immediate?: boolean): Timer;
144 | debounce(method: (arg1: A) => void, arg1: A, wait: number | string, immediate?: boolean): Timer;
145 | debounce(method: (arg1: A, arg2: B) => void, arg1: A, arg2: B, wait: number | string, immediate?: boolean): Timer;
146 | debounce(method: (arg1: A, arg2: B, arg3: C) => void, arg1: A, arg2: B, arg3: C, wait: number | string, immediate?: boolean): Timer;
147 | cancelTimers(): void;
148 | hasTimers(): boolean;
149 | cancel(timer?: any): any;
150 | ensureInstance(): void;
151 | /**
152 | * Returns debug information related to the current instance of Backburner
153 | *
154 | * @method getDebugInfo
155 | * @returns {Object | undefined} Will return and Object containing debug information if
156 | * the DEBUG flag is set to true on the current instance of Backburner, else undefined.
157 | */
158 | getDebugInfo(): {
159 | autorun: Error | null | undefined;
160 | counters: {
161 | begin: number;
162 | end: number;
163 | events: {
164 | begin: number;
165 | end: number;
166 | };
167 | autoruns: {
168 | created: number;
169 | completed: number;
170 | };
171 | run: number;
172 | join: number;
173 | defer: number;
174 | schedule: number;
175 | scheduleIterable: number;
176 | deferOnce: number;
177 | scheduleOnce: number;
178 | setTimeout: number;
179 | later: number;
180 | throttle: number;
181 | debounce: number;
182 | cancelTimers: number;
183 | cancel: number;
184 | loops: {
185 | total: number;
186 | nested: number;
187 | };
188 | };
189 | timers: import("./backburner/interfaces").IQueueItem[];
190 | instanceStack: (import("./backburner/deferred-action-queues").IDebugInfo | null | undefined)[];
191 | } | undefined;
192 | private _end;
193 | private _join;
194 | private _run;
195 | private _cancelAutorun;
196 | private _later;
197 | private _cancelLaterTimer;
198 | /**
199 | Trigger an event. Supports up to two arguments. Designed around
200 | triggering transition events from one run loop instance to the
201 | next, which requires an argument for the instance and then
202 | an argument for the next instance.
203 |
204 | @private
205 | @method _trigger
206 | @param {String} eventName
207 | @param {any} arg1
208 | @param {any} arg2
209 | */
210 | private _trigger;
211 | private _runExpiredTimers;
212 | private _scheduleExpiredTimers;
213 | private _reinstallTimerTimeout;
214 | private _clearTimerTimeout;
215 | private _installTimerTimeout;
216 | private _ensureInstance;
217 | private _scheduleAutorun;
218 | private createTask;
219 | }
220 |
--------------------------------------------------------------------------------
/tests/queue-push-unique-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | let Queue = (Backburner as any).Queue;
4 |
5 | QUnit.module('tests/queue-push-unique');
6 | let slice = [].slice;
7 |
8 | QUnit.test('pushUnique: 2 different targets', function(assert) {
9 | let queue = new Queue('foo');
10 | let target1fooWasCalled: string[][] = [];
11 | let target2fooWasCalled: string[][] = [];
12 | let target1 = {
13 | foo() {
14 | target1fooWasCalled.push(slice.call(arguments));
15 | }
16 | };
17 |
18 | let target2 = {
19 | foo() {
20 | target2fooWasCalled.push(slice.call(arguments));
21 | }
22 | };
23 |
24 | queue.pushUnique(target1, target1.foo, ['a']);
25 | queue.pushUnique(target2, target2.foo, ['b']);
26 |
27 | assert.deepEqual(target1fooWasCalled, []);
28 | assert.deepEqual(target2fooWasCalled, []);
29 |
30 | queue.flush();
31 |
32 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
33 | assert.deepEqual(target1fooWasCalled[0], ['a']);
34 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once');
35 | assert.deepEqual(target2fooWasCalled[0], ['b']);
36 | });
37 |
38 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) {
39 | let queue = new Queue('foo');
40 | let target1fooWasCalled: string[][] = [];
41 | let target1barWasCalled: string[][] = [];
42 | let target1 = {
43 | foo: function() {
44 | target1fooWasCalled.push(slice.call(arguments));
45 | },
46 | bar: function() {
47 | target1barWasCalled.push(slice.call(arguments));
48 | }
49 | };
50 |
51 | queue.pushUnique(target1, target1.foo, ['a']);
52 | queue.pushUnique(target1, target1.bar, ['b']);
53 |
54 | assert.deepEqual(target1fooWasCalled, []);
55 | assert.deepEqual(target1barWasCalled, []);
56 |
57 | queue.flush();
58 |
59 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
60 | assert.deepEqual(target1fooWasCalled[0], ['a']);
61 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once');
62 | assert.deepEqual(target1barWasCalled[0], ['b']);
63 | });
64 |
65 | QUnit.test('pushUnique: 1 target, 1 different methods called twice', function(assert) {
66 | let queue = new Queue('foo');
67 | let target1fooWasCalled: string[][] = [];
68 | let target1 = {
69 | foo: function() {
70 | target1fooWasCalled.push(slice.call(arguments));
71 | }
72 | };
73 |
74 | queue.pushUnique(target1, target1.foo, ['a']);
75 | queue.pushUnique(target1, target1.foo, ['b']);
76 |
77 | assert.deepEqual(target1fooWasCalled, []);
78 |
79 | queue.flush();
80 |
81 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
82 | assert.deepEqual(target1fooWasCalled[0], ['b']);
83 | });
84 |
85 | QUnit.test('pushUnique: 2 different targets', function(assert) {
86 | let queue = new Queue('foo', {});
87 | let target1fooWasCalled: string[][] = [];
88 | let target2fooWasCalled: string[][] = [];
89 | let target1 = {
90 | foo: function() {
91 | target1fooWasCalled.push(slice.call(arguments));
92 | }
93 | };
94 |
95 | let target2 = {
96 | foo: function() {
97 | target2fooWasCalled.push(slice.call(arguments));
98 | }
99 | };
100 |
101 | queue.pushUnique(target1, target1.foo, ['a']);
102 | queue.pushUnique(target2, target2.foo, ['b']);
103 |
104 | assert.deepEqual(target1fooWasCalled, []);
105 | assert.deepEqual(target2fooWasCalled, []);
106 |
107 | queue.flush();
108 |
109 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
110 | assert.deepEqual(target1fooWasCalled[0], ['a']);
111 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once');
112 | assert.deepEqual(target2fooWasCalled[0], ['b']);
113 | });
114 |
115 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) {
116 | let queue = new Queue('foo', {});
117 | let target1fooWasCalled: string[][] = [];
118 | let target1barWasCalled: string[][] = [];
119 | let target1 = {
120 | foo: function() {
121 | target1fooWasCalled.push(slice.call(arguments));
122 | },
123 | bar: function() {
124 | target1barWasCalled.push(slice.call(arguments));
125 | }
126 | };
127 |
128 | queue.pushUnique(target1, target1.foo, ['a']);
129 | queue.pushUnique(target1, target1.bar, ['b']);
130 |
131 | assert.deepEqual(target1fooWasCalled, []);
132 | assert.deepEqual(target1barWasCalled, []);
133 |
134 | queue.flush();
135 |
136 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
137 | assert.deepEqual(target1fooWasCalled[0], ['a']);
138 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once');
139 | assert.deepEqual(target1barWasCalled[0], ['b']);
140 | });
141 |
142 | QUnit.test('pushUnique: 1 target, 1 diffe`rent methods called twice', function(assert) {
143 | let queue = new Queue('foo', {});
144 | let target1fooWasCalled: string[][] = [];
145 | let target1 = {
146 | foo: function() {
147 | target1fooWasCalled.push(slice.call(arguments));
148 | }
149 | };
150 |
151 | queue.pushUnique(target1, target1.foo, ['a']);
152 | queue.pushUnique(target1, target1.foo, ['b']);
153 |
154 | assert.deepEqual(target1fooWasCalled, []);
155 |
156 | queue.flush();
157 |
158 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
159 | assert.deepEqual(target1fooWasCalled[0], ['b']);
160 | });
161 |
162 | QUnit.test('pushUnique: 1 target, 2 different methods, second one called twice', function(assert) {
163 | let queue = new Queue('foo', {});
164 | let target1barWasCalled: string[][] = [];
165 | let target1 = {
166 | foo: function() {
167 | },
168 | bar: function() {
169 | target1barWasCalled.push(slice.call(arguments));
170 | }
171 | };
172 |
173 | queue.pushUnique(target1, target1.foo);
174 | queue.pushUnique(target1, target1.bar, ['a']);
175 | queue.pushUnique(target1, target1.bar, ['b']);
176 |
177 | assert.deepEqual(target1barWasCalled, []);
178 |
179 | queue.flush();
180 |
181 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once');
182 | });
183 |
184 | QUnit.test('pushUnique: 2 different targets', function(assert) {
185 | let queue = new Queue('foo', {});
186 |
187 | let target1fooWasCalled: string[][] = [];
188 | let target2fooWasCalled: string[][] = [];
189 | let target1 = {
190 | foo: function() {
191 | target1fooWasCalled.push(slice.call(arguments));
192 | }
193 | };
194 |
195 | let target2 = {
196 | foo: function() {
197 | target2fooWasCalled.push(slice.call(arguments));
198 | }
199 | };
200 | queue.pushUnique(target1, target1.foo, ['a']);
201 | queue.pushUnique(target2, target2.foo, ['b']);
202 |
203 | assert.deepEqual(target1fooWasCalled, []);
204 | assert.deepEqual(target2fooWasCalled, []);
205 |
206 | queue.flush();
207 |
208 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
209 | assert.deepEqual(target1fooWasCalled[0], ['a']);
210 | assert.deepEqual(target2fooWasCalled.length, 1, 'expected: target 2.foo to be called only once');
211 | assert.deepEqual(target2fooWasCalled[0], ['b']);
212 | });
213 |
214 | QUnit.test('pushUnique: 1 target, 2 different methods', function(assert) {
215 | let queue = new Queue('foo', {});
216 | let target1fooWasCalled: string[][] = [];
217 | let target1barWasCalled: string[][] = [];
218 | let target1 = {
219 | foo: function() {
220 | target1fooWasCalled.push(slice.call(arguments));
221 | },
222 | bar: function() {
223 | target1barWasCalled.push(slice.call(arguments));
224 | }
225 | };
226 |
227 | queue.pushUnique(target1, target1.foo, ['a']);
228 | queue.pushUnique(target1, target1.bar, ['b']);
229 |
230 | assert.deepEqual(target1fooWasCalled, []);
231 | assert.deepEqual(target1barWasCalled, []);
232 |
233 | queue.flush();
234 |
235 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
236 | assert.deepEqual(target1fooWasCalled[0], ['a']);
237 | assert.deepEqual(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once');
238 | assert.deepEqual(target1barWasCalled[0], ['b']);
239 | });
240 |
241 | QUnit.test('pushUnique: 1 target, 1 different methods called twice', function(assert) {
242 | let queue = new Queue('foo', {});
243 | let target1fooWasCalled: string[][] = [];
244 | let target1 = {
245 | foo: function() {
246 | target1fooWasCalled.push(slice.call(arguments));
247 | }
248 | };
249 |
250 | queue.pushUnique(target1, target1.foo, ['a']);
251 | queue.pushUnique(target1, target1.foo, ['b']);
252 |
253 | assert.deepEqual(target1fooWasCalled, []);
254 |
255 | queue.flush();
256 |
257 | assert.deepEqual(target1fooWasCalled.length, 1, 'expected: target 1.foo to be called only once');
258 | assert.deepEqual(target1fooWasCalled[0], ['b']);
259 | });
260 |
261 | QUnit.test('pushUnique: 1 target, 2 different methods, second one called twice', function(assert) {
262 | let queue = new Queue('foo', {});
263 |
264 | let target1barWasCalled: string[][] = [];
265 | let target1 = {
266 | foo: function() {
267 | },
268 | bar: function() {
269 | target1barWasCalled.push(slice.call(arguments));
270 | }
271 | };
272 |
273 | queue.pushUnique(target1, target1.foo);
274 | queue.pushUnique(target1, target1.bar, ['a']);
275 | queue.pushUnique(target1, target1.bar, ['b']);
276 |
277 | assert.deepEqual(target1barWasCalled, []);
278 |
279 | queue.flush();
280 |
281 | assert.equal(target1barWasCalled.length, 1, 'expected: target 1.bar to be called only once');
282 | });
283 |
284 | QUnit.test('can cancel property', function(assert) {
285 | let queue = new Queue('foo', {});
286 |
287 | let target1fooWasCalled: number = 0;
288 | let target2fooWasCalled: number = 0;
289 | let target1 = {
290 | foo: function() {
291 | target1fooWasCalled++;
292 | }
293 | };
294 |
295 | let target2 = {
296 | foo: function() {
297 | target2fooWasCalled++;
298 | }
299 | };
300 |
301 | let timer1 = queue.pushUnique(target1, target1.foo);
302 | let timer2 = queue.pushUnique(target2, target2.foo);
303 |
304 | queue.cancel(timer2);
305 | queue.cancel(timer1);
306 |
307 | queue.pushUnique(target1, target1.foo);
308 | queue.pushUnique(target1, target1.foo);
309 | queue.pushUnique(target2, target2.foo);
310 | queue.pushUnique(target2, target2.foo);
311 |
312 | queue.flush();
313 |
314 | assert.equal(target1fooWasCalled, 1);
315 | assert.equal(target2fooWasCalled, 1);
316 | });
317 |
318 | QUnit.test('pushUnique: 1 target, 1 method called twice, canceled 2 call', function(assert) {
319 | let queue = new Queue('foo');
320 | let invocationArgs: string[][] = [];
321 | let target1 = {
322 | foo: function() {
323 | invocationArgs.push(...arguments);
324 | }
325 | };
326 |
327 | queue.pushUnique(target1, target1.foo, ['a']);
328 | let timer = queue.pushUnique(target1, target1.foo, ['b']);
329 |
330 | assert.deepEqual(invocationArgs, [], 'precond - empty initially');
331 |
332 | queue.cancel(timer);
333 |
334 | queue.flush();
335 |
336 | assert.deepEqual(invocationArgs, [], 'still has not been invoked');
337 | });
338 |
--------------------------------------------------------------------------------
/tests/later-test.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable*/
2 | import Backburner from 'backburner';
3 | import lolex from 'lolex';
4 |
5 | const originalDateNow = Date.now;
6 | const originalDateValueOf = Date.prototype.valueOf;
7 |
8 | let fakeClock;
9 | QUnit.module('tests/set-timeout-test', {
10 | afterEach() {
11 | Date.now = originalDateNow;
12 | Date.prototype.valueOf = originalDateValueOf;
13 | if (fakeClock) {
14 | fakeClock.uninstall();
15 | }
16 | }
17 | });
18 |
19 | QUnit.test('later', function(assert) {
20 | assert.expect(6);
21 |
22 | let bb = new Backburner(['one']);
23 | let step = 0;
24 | let instance;
25 | let done = assert.async();
26 |
27 | // Force +new Date to return the same result while scheduling
28 | // run.later timers. Otherwise: non-determinism!
29 | let now = +new Date();
30 | Date.prototype.valueOf = function() { return now; };
31 |
32 | bb.later(null, () => {
33 | instance = bb.currentInstance;
34 | assert.equal(step++, 0);
35 | }, 10);
36 |
37 | bb.later(null, () => {
38 | assert.equal(step++, 1);
39 | assert.equal(instance, bb.currentInstance, 'same instance');
40 | }, 10);
41 |
42 | Date.prototype.valueOf = originalDateValueOf;
43 | // spin so that when we execute timers (+new Date()) will be greater than the
44 | // time scheduled above; not a problem in real life as we will never 'wait'
45 | // 0ms
46 | while ((+ new Date()) <= now + 10) {}
47 |
48 | bb.later(null, () => {
49 | assert.equal(step++, 2);
50 |
51 | bb.later(null, () => {
52 | assert.equal(step++, 3);
53 | assert.ok(true, 'Another later will execute correctly');
54 | done();
55 | }, 1);
56 | }, 20);
57 | });
58 |
59 | QUnit.test('later should rely on stubbed `Date.now`', function(assert) {
60 | assert.expect(1);
61 |
62 | let bb = new Backburner(['one']);
63 | let done = assert.async();
64 | let globalNowWasUsed = false;
65 |
66 | Date.now = function() {
67 | globalNowWasUsed = true;
68 | return originalDateNow();
69 | };
70 |
71 | bb.later(() => {
72 | assert.ok(globalNowWasUsed);
73 | done();
74 | }, 1);
75 | });
76 |
77 | QUnit.test('later shedules timers correctly after time travel', function(assert) {
78 | assert.expect(2);
79 |
80 | let bb = new Backburner(['one']);
81 | let done = assert.async();
82 | let start = originalDateNow();
83 | let now = start;
84 |
85 | Date.now = () => now;
86 |
87 | let called1At = 0;
88 | let called2At = 0;
89 |
90 | bb.later(() => called1At = originalDateNow(), 1000);
91 |
92 | now += 1000;
93 |
94 | bb.later(() => called2At = originalDateNow(), 10);
95 |
96 | now += 10;
97 |
98 | setTimeout(() => {
99 | assert.ok(called1At !== 0, 'timeout 1 was called');
100 | assert.ok(called2At !== 0, 'timeout 2 was called');
101 | done();
102 | }, 20);
103 | });
104 |
105 | let bb;
106 | QUnit.module('later arguments / arity', {
107 | beforeEach() {
108 | bb = new Backburner(['one']);
109 | },
110 | afterEach() {
111 | bb = undefined;
112 | if (fakeClock) {
113 | fakeClock.uninstall();
114 | }
115 | }
116 | });
117 |
118 | QUnit.test('[callback]', function(assert) {
119 | assert.expect(2);
120 |
121 | let done = assert.async();
122 |
123 | bb.later(function() {
124 | assert.equal(arguments.length, 0);
125 | assert.ok(true, 'was called');
126 | done();
127 | });
128 | });
129 |
130 | QUnit.test('[callback, undefined]', function(assert) {
131 | assert.expect(2);
132 | let done = assert.async();
133 |
134 | bb.later(function() {
135 | assert.equal(arguments.length, 1);
136 | assert.ok(true, 'was called');
137 | done();
138 | }, undefined);
139 | });
140 |
141 | QUnit.test('[null, callback, undefined]', function(assert) {
142 | assert.expect(2);
143 | let done = assert.async();
144 |
145 | bb.later(null, function() {
146 | assert.equal(arguments.length, 0);
147 | assert.ok(true, 'was called');
148 | done();
149 | });
150 | });
151 |
152 | QUnit.test('[null, callback, undefined]', function(assert) {
153 | assert.expect(2);
154 | let done = assert.async();
155 |
156 | bb.later(null, function() {
157 | assert.equal(arguments.length, 1);
158 | assert.ok(true, 'was called');
159 | done();
160 | }, undefined);
161 | });
162 |
163 | QUnit.test('[null, callback, null]', function(assert) {
164 | assert.expect(3);
165 |
166 | let done = assert.async();
167 |
168 | bb.later(null, function() {
169 | assert.equal(arguments.length, 1);
170 | assert.equal(arguments[0], null);
171 | assert.ok(true, 'was called');
172 | done();
173 | }, null);
174 | });
175 |
176 | QUnit.test('[callback, string, string, string]', function(assert) {
177 | assert.expect(5);
178 |
179 | let done = assert.async();
180 |
181 | bb.later(function() {
182 | assert.equal(arguments.length, 3);
183 | assert.equal(arguments[0], 'a');
184 | assert.equal(arguments[1], 'b');
185 | assert.equal(arguments[2], 'c');
186 | assert.ok(true, 'was called');
187 | done();
188 | }, 'a', 'b', 'c');
189 | });
190 |
191 | QUnit.test('[null, callback, string, string, string]', function(assert) {
192 | assert.expect(5);
193 |
194 | let done = assert.async();
195 |
196 | bb.later(null, function() {
197 | assert.equal(arguments.length, 3);
198 | assert.equal(arguments[0], 'a');
199 | assert.equal(arguments[1], 'b');
200 | assert.equal(arguments[2], 'c');
201 | assert.ok(true, 'was called');
202 | done();
203 | }, 'a', 'b', 'c');
204 | });
205 |
206 | QUnit.test('[null, callback, string, string, string, number]', function(assert) {
207 | assert.expect(5);
208 | let done = assert.async();
209 | bb.later(null, function() {
210 | assert.equal(arguments.length, 3);
211 | assert.equal(arguments[0], 'a');
212 | assert.equal(arguments[1], 'b');
213 | assert.equal(arguments[2], 'c');
214 | assert.ok(true, 'was called');
215 | done();
216 | }, 'a', 'b', 'c', 10);
217 | });
218 |
219 | QUnit.test('[null, callback, string, string, string, numericString]', function(assert) {
220 | assert.expect(5);
221 | let done = assert.async();
222 | bb.later(null, function() {
223 | assert.equal(arguments.length, 3);
224 | assert.equal(arguments[0], 'a');
225 | assert.equal(arguments[1], 'b');
226 | assert.equal(arguments[2], 'c');
227 | assert.ok(true, 'was called');
228 | done();
229 | }, 'a', 'b', 'c', '1');
230 | });
231 |
232 | QUnit.test('[obj, string]', function(assert) {
233 | assert.expect(1);
234 | let done = assert.async();
235 | bb.later({
236 | bro() {
237 | assert.ok(true, 'was called');
238 | done();
239 | }
240 | }, 'bro');
241 | });
242 |
243 | QUnit.test('[obj, string, value]', function(assert) {
244 | assert.expect(3);
245 | let done = assert.async();
246 | bb.later({
247 | bro() {
248 | assert.equal(arguments.length, 1);
249 | assert.equal(arguments[0], 'value');
250 | assert.ok(true, 'was called');
251 | done();
252 | }
253 | }, 'bro', 'value');
254 | });
255 |
256 | QUnit.test('[obj, string, value, number]', function(assert) {
257 | let done = assert.async();
258 | bb.later({
259 | bro() {
260 | assert.equal(arguments.length, 1);
261 | assert.equal(arguments[0], 'value');
262 | assert.ok(true, 'was called');
263 | done();
264 | }
265 | }, 'bro', 'value', 1);
266 | });
267 |
268 | QUnit.test('[obj, string, value, numericString]', function(assert) {
269 | let done = assert.async();
270 | bb.later({
271 | bro() {
272 | assert.equal(arguments.length, 1);
273 | assert.equal(arguments[0], 'value');
274 | assert.ok(true, 'was called');
275 | done();
276 | }
277 | }, 'bro', 'value', '1');
278 | });
279 |
280 | QUnit.test('onError', function(assert) {
281 | assert.expect(1);
282 |
283 | let done = assert.async();
284 |
285 | function onError(error) {
286 | assert.equal('test error', error.message);
287 | done();
288 | }
289 |
290 | bb = new Backburner(['errors'], { onError });
291 |
292 | bb.later(() => { throw new Error('test error'); }, 1);
293 | });
294 |
295 | QUnit.test('later doesn\'t trigger twice with earlier later', function(assert) {
296 | assert.expect(4);
297 |
298 | bb = new Backburner(['one']);
299 | let called1 = 0;
300 | let called2 = 0;
301 | let beginCalls = 0;
302 | let endCalls = 0;
303 | let oldBegin = bb.begin;
304 | let oldEnd = bb.end;
305 | let done = assert.async();
306 |
307 | bb.begin = function() {
308 | beginCalls++;
309 | oldBegin.call(bb);
310 | };
311 |
312 | bb.end = function() {
313 | endCalls++;
314 | oldEnd.call(bb);
315 | };
316 |
317 | bb.later(() => called1++, 50);
318 | bb.later(() => called2++, 10);
319 |
320 | setTimeout(() => {
321 | assert.equal(called1, 1, 'timeout 1 was called once');
322 | assert.equal(called2, 1, 'timeout 2 was called once');
323 | assert.equal(beginCalls, 2, 'begin() was called twice');
324 | assert.equal(endCalls, 2, 'end() was called twice');
325 | done();
326 | }, 100);
327 | });
328 |
329 | QUnit.test('later with two Backburner instances', function(assert) {
330 | assert.expect(8);
331 |
332 | let steps = 0;
333 | let done = assert.async();
334 | let bb1 = new Backburner(['one'], {
335 | onBegin() {
336 | assert.equal(++steps, 4);
337 | }
338 | });
339 | let bb2 = new Backburner(['one'], {
340 | onBegin() {
341 | assert.equal(++steps, 6);
342 | }
343 | });
344 |
345 | assert.equal(++steps, 1);
346 |
347 | bb1.later(() => assert.equal(++steps, 5), 10);
348 |
349 | assert.equal(++steps, 2);
350 |
351 | bb2.later(() => assert.equal(++steps, 7), 10);
352 |
353 | assert.equal(++steps, 3);
354 |
355 | setTimeout(() => {
356 | assert.equal(++steps, 8);
357 | done();
358 | }, 50);
359 | });
360 |
361 | QUnit.test('expired timeout doesn\'t hang when setting a new timeout', function(assert) {
362 | assert.expect(3);
363 |
364 | let called1At = 0;
365 | let called2At = 0;
366 | let done = assert.async();
367 |
368 | bb.later(() => called1At = Date.now(), 1);
369 |
370 | // Block JS to simulate https://github.com/ebryn/backburner.js/issues/135
371 | let waitUntil = Date.now() + 5;
372 | while (Date.now() < waitUntil) { }
373 |
374 | bb.later(() => called2At = Date.now(), 50);
375 |
376 | setTimeout(() => {
377 | assert.ok(called1At !== 0, 'timeout 1 was called');
378 | assert.ok(called2At !== 0, 'timeout 2 was called');
379 | assert.ok(called2At - called1At > 10, 'timeout 1 did not wait for timeout 2');
380 | done();
381 | }, 60);
382 | });
383 |
384 | QUnit.test('NaN timeout doesn\'t hang other timeouts', function(assert) {
385 | assert.expect(2);
386 |
387 | let done = assert.async();
388 | let called1At = 0;
389 | let called2At = 0;
390 |
391 | bb.later(() => called1At = Date.now(), 1);
392 | bb.later(() => {}, NaN);
393 | bb.later(() => called2At = Date.now(), 10);
394 |
395 | setTimeout(() => {
396 | assert.ok(called1At !== 0, 'timeout 1 was called');
397 | assert.ok(called2At !== 0, 'timeout 2 was called');
398 | done();
399 | }, 20);
400 | });
401 |
402 | QUnit.test('when [callback, string] args passed', function(assert) {
403 | assert.expect(1);
404 | let done = assert.async();
405 |
406 | let bb = new Backburner(['one']);
407 |
408 | bb.later(function(name) {
409 | assert.equal(name, 'batman');
410 | done();
411 | }, 'batman', 0);
412 | });
413 |
414 | QUnit.test('can be ran "early" with fake timers GH#351', function(assert) {
415 | assert.expect(1);
416 | let done = assert.async();
417 |
418 | let bb = new Backburner(['one']);
419 |
420 | fakeClock = lolex.install();
421 |
422 | let startTime = originalDateNow();
423 | bb.later(function(name) {
424 | let endTime = originalDateNow();
425 | assert.ok(endTime - startTime < 100, 'did not wait for 5s to run timer');
426 | done();
427 | }, 5000);
428 |
429 | fakeClock.tick(5001);
430 | });
431 |
432 | QUnit.test('debounce called before later', function(assert) {
433 | assert.expect(1);
434 |
435 | let done = assert.async(1);
436 | let bb = new Backburner(['one']);
437 | let func = function() {};
438 |
439 | bb.run(() => {
440 | bb.debounce(func, 1000);
441 | setTimeout(function() {
442 | bb.debounce(func, 1000);
443 | }, 50);
444 |
445 | let before = Date.now();
446 |
447 | bb.later(function() {
448 | let diff = Date.now() - before;
449 | assert.ok(diff < 1010, '.later called with too much delay');
450 | done();
451 | }, 1000);
452 | });
453 |
454 | });
455 |
456 | QUnit.test('boundRunExpiredTimers is called once when first timer canceled', function(assert) {
457 | let done = assert.async(1);
458 |
459 | let bb = new Backburner(['one']);
460 |
461 | let timer = bb.later(function() {}, 500);
462 | bb.cancel(timer);
463 |
464 | let boundRunExpiredTimers = bb['_boundRunExpiredTimers'];
465 | bb['_boundRunExpiredTimers'] = function() {
466 | assert.ok(true);
467 | done();
468 | return boundRunExpiredTimers.apply(bb, arguments);
469 | };
470 |
471 | bb.later(function() {}, 800);
472 | });
473 |
--------------------------------------------------------------------------------
/tests/throttle-test.ts:
--------------------------------------------------------------------------------
1 | import Backburner from 'backburner';
2 |
3 | QUnit.module('tests/throttle');
4 |
5 | QUnit.test('throttle', function(assert) {
6 | assert.expect(18);
7 |
8 | let bb = new Backburner(['zomg']);
9 | let step = 0;
10 | let done = assert.async();
11 |
12 | let wasCalled = false;
13 | function throttler() {
14 | assert.ok(!wasCalled);
15 | wasCalled = true;
16 | }
17 |
18 | // let's throttle the function `throttler` for 40ms
19 | // it will be executed in 40ms
20 | bb.throttle(null, throttler, 40, false);
21 | assert.equal(step++, 0);
22 |
23 | // let's schedule `throttler` to run in 10ms
24 | setTimeout(() => {
25 | assert.equal(step++, 1);
26 | assert.ok(!wasCalled);
27 | bb.throttle(null, throttler, false);
28 | }, 10);
29 |
30 | // let's schedule `throttler` to run again in 20ms
31 | setTimeout(() => {
32 | assert.equal(step++, 2);
33 | assert.ok(!wasCalled);
34 | bb.throttle(null, throttler, false);
35 | }, 20);
36 |
37 | // let's schedule `throttler` to run yet again in 30ms
38 | setTimeout(() => {
39 | assert.equal(step++, 3);
40 | assert.ok(!wasCalled);
41 | bb.throttle(null, throttler, false);
42 | }, 30);
43 |
44 | // at 40ms, `throttler` will get called once
45 |
46 | // now, let's schedule an assertion to occur at 50ms,
47 | // 10ms after `throttler` has been called
48 | setTimeout(() => {
49 | assert.equal(step++, 4);
50 | assert.ok(wasCalled);
51 | }, 50);
52 |
53 | // great, we've made it this far, there's one more thing
54 | // we need to test. we want to make sure we can call `throttle`
55 | // again with the same target/method after it has executed
56 | // at the 60ms mark, let's schedule another call to `throttle`
57 | setTimeout(() => {
58 | wasCalled = false; // reset the flag
59 |
60 | // assert call order
61 | assert.equal(step++, 5);
62 |
63 | // call throttle for the second time
64 | bb.throttle(null, throttler, 100, false);
65 |
66 | // assert that it is called in the future and not blackholed
67 | setTimeout(() => {
68 | assert.equal(step++, 6);
69 | assert.ok(wasCalled, 'Another throttle call with the same function can be executed later');
70 | }, 110);
71 | }, 60);
72 |
73 | setTimeout(() => {
74 | wasCalled = false; // reset the flag
75 |
76 | // assert call order
77 | assert.equal(step++, 7);
78 |
79 | // call throttle again that time using a string number like time interval
80 | bb.throttle(null, throttler, '100', false);
81 |
82 | // assert that it is called in the future and not blackholed
83 | setTimeout(() => {
84 | assert.equal(step++, 8);
85 | assert.ok(wasCalled, 'Throttle accept a string number like time interval');
86 | done();
87 | }, 110);
88 | }, 180);
89 | });
90 |
91 | QUnit.test('throttle with cancelTimers', function(assert) {
92 | assert.expect(1);
93 |
94 | let count = 0;
95 | let bb = new Backburner(['zomg']);
96 |
97 | // Throttle a no-op 10ms
98 | bb.throttle(null, () => { /* no-op */ }, 10, false);
99 |
100 | try {
101 | bb.cancelTimers();
102 | } catch (e) {
103 | count++;
104 | }
105 |
106 | assert.equal(count, 0, 'calling cancelTimers while something is being throttled does not throw an error');
107 | });
108 |
109 | QUnit.test('throttled function is called with final argument', function(assert) {
110 | assert.expect(1);
111 | let done = assert.async();
112 |
113 | let count = 0;
114 | let bb = new Backburner(['zomg']);
115 |
116 | function throttled(arg) {
117 | assert.equal(arg, 'bus');
118 | done();
119 | }
120 |
121 | bb.throttle(null, throttled, 'car' , 10, false);
122 | bb.throttle(null, throttled, 'bicycle' , 10, false);
123 | bb.throttle(null, throttled, 'bus' , 10, false);
124 | });
125 |
126 | QUnit.test('throttle returns same timer', function(assert) {
127 | assert.expect(2);
128 |
129 | let bb = new Backburner(['joker']);
130 |
131 | function throttled1() {}
132 | function throttled2() {}
133 |
134 | let timer1 = bb.throttle(null, throttled1, 10);
135 | let timer2 = bb.throttle(null, throttled2, 10);
136 | let timer3 = bb.throttle(null, throttled1, 10);
137 | let timer4 = bb.throttle(null, throttled2, 10);
138 |
139 | assert.equal(timer1, timer3);
140 | assert.equal(timer2, timer4);
141 | });
142 |
143 | QUnit.test('throttle leading edge', function(assert) {
144 | assert.expect(10);
145 |
146 | let bb = new Backburner(['zerg']);
147 | let throttle;
148 | let throttle2;
149 | let wasCalled = false;
150 | let done = assert.async();
151 |
152 | function throttler() {
153 | assert.ok(!wasCalled, 'throttler hasn\'t been called yet');
154 | wasCalled = true;
155 | }
156 |
157 | // let's throttle the function `throttler` for 40ms
158 | // it will be executed immediately, but throttled for the future hits
159 | throttle = bb.throttle(null, throttler, 40);
160 |
161 | assert.ok(wasCalled, 'function was executed immediately');
162 |
163 | wasCalled = false;
164 | // let's schedule `throttler` to run again, it shouldn't be allowed to queue for another 40 msec
165 | throttle2 = bb.throttle(null, throttler, 40);
166 |
167 | assert.equal(throttle, throttle2, 'No new throttle was inserted, returns old throttle');
168 |
169 | setTimeout(() => {
170 | assert.ok(!wasCalled, 'attempt to call throttle again didn\'t happen');
171 |
172 | throttle = bb.throttle(null, throttler, 40);
173 | assert.ok(wasCalled, 'newly inserted throttle after timeout functioned');
174 |
175 | assert.ok(bb.cancel(throttle), 'wait time of throttle was cancelled');
176 |
177 | wasCalled = false;
178 | throttle2 = bb.throttle(null, throttler, 40);
179 | assert.notEqual(throttle, throttle2, 'the throttle is different');
180 | assert.ok(wasCalled, 'throttle was inserted and run immediately after cancel');
181 | done();
182 | }, 60);
183 | });
184 |
185 | QUnit.test('throttle returns timer information usable for cancelling', function(assert) {
186 | assert.expect(3);
187 |
188 | let done = assert.async();
189 | let bb = new Backburner(['batman']);
190 | let wasCalled = false;
191 |
192 | function throttler() {
193 | assert.ok(false, 'this method shouldn\'t be called');
194 | wasCalled = true;
195 | }
196 |
197 | let timer = bb.throttle(null, throttler, 1, false);
198 |
199 | assert.ok(bb.cancel(timer), 'the timer is cancelled');
200 |
201 | // should return false second time around
202 | assert.ok(!bb.cancel(timer), 'the timer no longer exists in the list');
203 |
204 | setTimeout(() => {
205 | assert.ok(!wasCalled, 'the timer wasn\'t called after waiting');
206 | done();
207 | }, 60);
208 | });
209 |
210 | QUnit.test('throttler cancel after it\'s executed returns false', function(assert) {
211 | assert.expect(3);
212 |
213 | let bb = new Backburner(['darkknight']);
214 | let done = assert.async();
215 |
216 | let wasCalled = false;
217 |
218 | function throttler() {
219 | assert.ok(true, 'the throttled method was called');
220 | wasCalled = true;
221 | }
222 |
223 | let timer = bb.throttle(null, throttler, 1);
224 |
225 | setTimeout(() => {
226 | assert.ok(!bb.cancel(timer), 'no timer existed to cancel');
227 | assert.ok(wasCalled, 'the timer was actually called');
228 | done();
229 | }, 10);
230 | });
231 |
232 | QUnit.test('throttler returns the appropriate timer to cancel if the old item still exists', function(assert) {
233 | assert.expect(5);
234 |
235 | let bb = new Backburner(['robin']);
236 | let wasCalled = false;
237 | let done = assert.async();
238 |
239 | function throttler() {
240 | assert.ok(true, 'the throttled method was called');
241 | wasCalled = true;
242 | }
243 |
244 | let timer = bb.throttle(null, throttler, 1);
245 | let timer2 = bb.throttle(null, throttler, 1);
246 |
247 | assert.deepEqual(timer, timer2, 'the same timer was returned');
248 |
249 | setTimeout(() => {
250 | bb.throttle(null, throttler, 1);
251 | assert.ok(!bb.cancel(timer), 'the second timer isn\'t removed, despite appearing to be the same item');
252 | assert.ok(wasCalled, 'the timer was actually called');
253 | done();
254 | }, 10);
255 |
256 | });
257 |
258 | QUnit.test('throttle without a target, without args', function(assert) {
259 | let done = assert.async();
260 | let bb = new Backburner(['batman']);
261 | let calledCount = 0;
262 | let calledWith = new Array();
263 | function throttled(...args) {
264 | calledCount++;
265 | calledWith.push(args);
266 | }
267 |
268 | bb.throttle(throttled, 10);
269 | bb.throttle(throttled, 10);
270 | bb.throttle(throttled, 10);
271 | assert.equal(calledCount, 1, 'throttle method was called immediately');
272 | assert.deepEqual(calledWith, [ [] ], 'throttle method was called with the correct arguments');
273 |
274 | setTimeout(() => {
275 | bb.throttle(throttled, 10);
276 | assert.equal(calledCount, 1, 'throttle method was not called again within the time window');
277 | }, 0);
278 |
279 | setTimeout(() => {
280 | assert.equal(calledCount, 1, 'throttle method was was only called once');
281 | done();
282 | }, 20);
283 | });
284 |
285 | QUnit.test('throttle without a target, without args - can be canceled', function(assert) {
286 | let bb = new Backburner(['batman']);
287 |
288 | let fooCalledCount = 0;
289 | let barCalledCount = 0;
290 | function foo() {
291 | fooCalledCount++;
292 | }
293 | function bar() {
294 | barCalledCount++;
295 | }
296 |
297 | bb.throttle(foo, 10);
298 | bb.throttle(foo, 10);
299 | assert.equal(fooCalledCount, 1, 'foo was called immediately, then throttle');
300 |
301 | bb.throttle(bar, 10);
302 | let timer = bb.throttle(bar, 10);
303 | assert.equal(barCalledCount, 1, 'bar was called immediately, then throttle');
304 |
305 | bb.cancel(timer);
306 | bb.throttle(bar, 10);
307 | assert.equal(barCalledCount, 2, 'after canceling the prior throttle, bar was called again');
308 | });
309 |
310 | QUnit.test('throttle without a target, without args, not immediate', function(assert) {
311 | let done = assert.async();
312 | let bb = new Backburner(['batman']);
313 | let calledCount = 0;
314 | let calledWith = new Array();
315 | function throttled(...args) {
316 | calledCount++;
317 | calledWith.push(args);
318 | }
319 |
320 | bb.throttle(throttled, 10, false);
321 | bb.throttle(throttled, 10, false);
322 | bb.throttle(throttled, 10, false);
323 | assert.equal(calledCount, 0, 'throttle method was not called immediately');
324 |
325 | setTimeout(() => {
326 | assert.equal(calledCount, 0, 'throttle method was not called in next tick');
327 | bb.throttle(throttled, 10, false);
328 | }, 0);
329 |
330 | setTimeout(() => {
331 | assert.equal(calledCount, 1, 'throttle method was was only called once');
332 | assert.deepEqual(calledWith, [ [] ], 'throttle method was called with the correct arguments');
333 | done();
334 | }, 20);
335 | });
336 |
337 | QUnit.test('throttle without a target, without args, not immediate - can be canceled', function(assert) {
338 | let done = assert.async();
339 | let bb = new Backburner(['batman']);
340 |
341 | let fooCalledCount = 0;
342 | let barCalledCount = 0;
343 | function foo() {
344 | fooCalledCount++;
345 | }
346 | function bar() {
347 | barCalledCount++;
348 | }
349 |
350 | bb.throttle(foo, 10, false);
351 | bb.throttle(foo, 10, false);
352 | assert.equal(fooCalledCount, 0, 'foo was not called immediately');
353 |
354 | bb.throttle(bar, 10, false);
355 | let timer = bb.throttle(bar, 10, false);
356 | assert.equal(barCalledCount, 0, 'bar was not called immediately');
357 |
358 | setTimeout(() => {
359 | assert.equal(fooCalledCount, 0, 'foo was not called within the time window');
360 | assert.equal(barCalledCount, 0, 'bar was not called within the time window');
361 |
362 | bb.cancel(timer);
363 | }, 0);
364 |
365 | setTimeout(() => {
366 | assert.equal(fooCalledCount, 1, 'foo ran');
367 | assert.equal(barCalledCount, 0, 'bar was properly canceled');
368 |
369 | bb.throttle(bar, 10, false);
370 |
371 | setTimeout(() => {
372 | assert.equal(barCalledCount, 1, 'bar was able to run after being canceled');
373 | done();
374 | }, 20);
375 | }, 20);
376 | });
377 |
378 | QUnit.test('throttle without a target, with args', function(assert) {
379 | let bb = new Backburner(['batman']);
380 | let calledWith: object[] = [];
381 | function throttled(first) {
382 | calledWith.push(first);
383 | }
384 |
385 | let foo = { isFoo: true };
386 | let bar = { isBar: true };
387 | let baz = { isBaz: true };
388 | bb.throttle(throttled, foo, 10);
389 | bb.throttle(throttled, bar, 10);
390 | bb.throttle(throttled, baz, 10);
391 |
392 | assert.deepEqual(calledWith, [{ isFoo: true }], 'throttle method was only called once, with correct argument');
393 | });
394 |
395 | QUnit.test('throttle without a target, with args - can be canceled', function(assert) {
396 | let done = assert.async();
397 | let bb = new Backburner(['batman']);
398 | let calledCount = 0;
399 | let calledWith: object[] = [];
400 | function throttled(first) {
401 | calledCount++;
402 | calledWith.push(first);
403 | }
404 |
405 | let foo = { isFoo: true };
406 | let bar = { isBar: true };
407 | let baz = { isBaz: true };
408 | let qux = { isQux: true };
409 | bb.throttle(throttled, foo, 10);
410 | bb.throttle(throttled, bar, 10);
411 | let timer = bb.throttle(throttled, baz, 10);
412 |
413 | assert.deepEqual(calledWith, [{ isFoo: true }], 'throttle method was only called once, with correct argument');
414 |
415 | setTimeout(() => {
416 | bb.cancel(timer);
417 | bb.throttle(throttled, qux, 10, true);
418 | assert.deepEqual(calledWith, [{ isFoo: true }, { isQux: true }], 'throttle method was called again after canceling prior timer');
419 | }, 0);
420 |
421 | setTimeout(() => {
422 | assert.deepEqual(calledWith, [{ isFoo: true }, { isQux: true }], 'throttle method was not called again');
423 | done();
424 | }, 20);
425 | });
426 |
427 | QUnit.test('throttle without a target, with args, not immediate', function(assert) {
428 | let done = assert.async();
429 | let bb = new Backburner(['batman']);
430 | let calledWith: object[] = [];
431 | function throttler(first) {
432 | calledWith.push(first);
433 | }
434 |
435 | let foo = { isFoo: true };
436 | let bar = { isBar: true };
437 | let baz = { isBaz: true };
438 | bb.throttle(throttler, foo, 10, false);
439 | bb.throttle(throttler, bar, 10, false);
440 | bb.throttle(throttler, baz, 10, false);
441 | assert.deepEqual(calledWith, [], 'throttler was not called immediately');
442 |
443 | setTimeout(() => {
444 | assert.deepEqual(calledWith, [{ isBaz: true }], 'debounce method was only called once, with correct argument');
445 | done();
446 | }, 20);
447 | });
448 |
449 | QUnit.test('throttle without a target, with args, not immediate - can be canceled', function(assert) {
450 | let done = assert.async();
451 | let bb = new Backburner(['batman']);
452 | let calledCount = 0;
453 | let calledWith: object[] = [];
454 | function throttled(first) {
455 | calledCount++;
456 | calledWith.push(first);
457 | }
458 |
459 | let foo = { isFoo: true };
460 | let bar = { isBar: true };
461 | let baz = { isBaz: true };
462 | bb.throttle(throttled, foo, 10, false);
463 | bb.throttle(throttled, bar, 10, false);
464 | let timer = bb.throttle(throttled, baz, 10, false);
465 | assert.equal(calledCount, 0, 'throttle method was not called immediately');
466 |
467 | setTimeout(() => {
468 | assert.deepEqual(calledWith, [], 'throttle method has not been called on next tick');
469 | bb.cancel(timer);
470 | }, 0);
471 |
472 | setTimeout(() => {
473 | assert.deepEqual(calledWith, [], 'throttle method is not called when canceled');
474 | done();
475 | }, 20);
476 | });
477 |
478 | QUnit.test('onError', function(assert) {
479 | assert.expect(1);
480 |
481 | function onError(error) {
482 | assert.equal('test error', error.message);
483 | }
484 |
485 | let bb = new Backburner(['errors'], {
486 | onError: onError
487 | });
488 |
489 | bb.throttle(null, () => {
490 | throw new Error('test error');
491 | }, 20);
492 | });
493 |
494 | QUnit.test('throttle + immediate joins existing run loop instances', function(assert) {
495 | assert.expect(1);
496 |
497 | function onError(error) {
498 | assert.equal('test error', error.message);
499 | }
500 |
501 | let bb = new Backburner(['errors'], {
502 | onError: onError
503 | });
504 |
505 | bb.run(() => {
506 | let parentInstance = bb.currentInstance;
507 | bb.throttle(null, () => {
508 | assert.equal(bb.currentInstance, parentInstance);
509 | }, 20, true);
510 | });
511 | });
512 |
513 | QUnit.test('when [callback, string] args passed', function(assert) {
514 | assert.expect(2);
515 |
516 | let bb = new Backburner(['one']);
517 | let functionWasCalled = false;
518 |
519 | bb.run(() => {
520 | bb.throttle(function(name) {
521 | assert.equal(name, 'batman');
522 | functionWasCalled = true;
523 | }, 'batman', 200);
524 | });
525 |
526 | assert.ok(functionWasCalled, 'function was called');
527 | });
528 |
--------------------------------------------------------------------------------