diff --git a/src/util/LoopController.js b/src/util/LoopController.js index 5597c8a..571634d 100644 --- a/src/util/LoopController.js +++ b/src/util/LoopController.js @@ -1,71 +1,107 @@ -let loopIsActive = false; -let animationFrame = null; -const loopRegister = []; - -class LoopController -{ - constructor() { - this.rafStep = this.rafStep.bind(this); - } - - getRegisteredItems() { - return [...loopRegister]; - } - - registerScrollbar(scrollbar) { - if (!loopRegister.includes(scrollbar)) { - loopRegister.push(scrollbar); - - this.start(); +function LoopControllerClass() { + /** + * @typedef {Object} Scrollbar + * @property {function} update + */ + + /** + * @type {Scrollbar[]} + */ + const scrollbarsRegister = []; + + /** + * true if loop is active + * @type {boolean} + */ + let isActive = false; + /** + * ID of requested animation frame + * @type {null|number} + */ + let animationFrameId = null; + + /** + * Function that called in animation frame + */ + const animationFrameCallback = () => { + if (!isActive) {return;} + + for (let scrollbar of scrollbarsRegister) { + scrollbar.update(); } - return this; + requestAnimationFrame(animationFrameCallback); }; - unregisterScrollbar(scrollbar) { - let index = loopRegister.indexOf(scrollbar); + /** + * Stop the loop if it wasn't active + * @return {LoopControllerClass} + */ + this.start = () => { + if (isActive) {return this;} - if (index !== -1) { - loopRegister.length === 1 && this.stop(); + isActive = true; - loopRegister.splice(index, 1); - } - - return this; + animationFrameId && cancelAnimationFrame(animationFrameId); + requestAnimationFrame(animationFrameCallback); }; + /** + * Stop the loop if it is active + * @return {LoopControllerClass} + */ + this.stop = () => { + if (!isActive) {return this;} - start() { - if (!loopIsActive) { - loopIsActive = true; + isActive = false; - animationFrame && cancelAnimationFrame(animationFrame); - animationFrame = requestAnimationFrame(this.rafStep); - } - - return this; + animationFrameId && cancelAnimationFrame(animationFrameId); + animationFrameId = null; }; - rafStep() { - if (!loopIsActive) {return;} + /** + * Return the array pf registered scrollbars + * @return {Scrollbar[]} + */ + this.getRegisteredScrollbars = () => { + return [...scrollbarsRegister]; + }; + /** + * Add the scrollbar to list to iterate each loop + * @param {Scrollbar} scrollbar + * @return {LoopControllerClass} + */ + this.registerScrollbar = (scrollbar) => { + if (scrollbarsRegister.indexOf(scrollbar) === -1) { + scrollbarsRegister.push(scrollbar); - for (let i = 0; i < loopRegister.length; i++) { - loopRegister[i].update(); + this.start(); } - animationFrame = requestAnimationFrame(this.rafStep); + return this; }; + /** + * Remove the scrollbar from list to iterate each loop + * @param {Scrollbar} scrollbar + * @return {LoopControllerClass} + */ + this.unregisterScrollbar = (scrollbar) => { + const index = scrollbarsRegister.indexOf(scrollbar); - stop() { - if (loopIsActive) { - loopIsActive = false; - - animationFrame && cancelAnimationFrame(animationFrame); + if (index !== -1) { + scrollbarsRegister.splice(index, 1); } return this; }; } -const instance = new LoopController(); +export const LoopController = new LoopControllerClass(); +export default LoopController; -export default instance; +/** + * Return new instance of LoopControllerClass + * @return {LoopControllerClass} + */ +export function createLoopController() { + return new LoopControllerClass(); +} diff --git a/tests/LoopController.spec.js b/tests/LoopController.spec.js index e0fe7af..3afb241 100644 --- a/tests/LoopController.spec.js +++ b/tests/LoopController.spec.js @@ -1,7 +1,7 @@ -import expect from "expect"; -import React from "react"; -import sinon from 'sinon'; -import LoopController from '../src/util/LoopController'; +import expect from "expect"; +import React from "react"; +import sinon from 'sinon'; +import { createLoopController, LoopController } from '../src/util/LoopController'; describe("LoopController", () => { const ScrollbarMock = { @@ -12,17 +12,18 @@ describe("LoopController", () => { it("should register the scrollbar", () => { LoopController.registerScrollbar(ScrollbarMock); - expect(LoopController.getRegisteredItems().length).toEqual(1); + expect(LoopController.getRegisteredScrollbars().length).toEqual(1); + expect(LoopController.getRegisteredScrollbars()[0]).toEqual(ScrollbarMock); }); it("should call an 'update' method of registered scrollbar", (done) => { setTimeout(() => { - expect(spy.callCount).toBeGreaterThan(10); + expect(spy.callCount).toBeGreaterThanOrEqual(30); done(); }, 500); }); - it("should call stop the loop when .stop() executed", (done) => { + it("should stop the loop when .stop() executed", (done) => { let callCount = spy.callCount * 1; LoopController.stop(); @@ -32,7 +33,7 @@ describe("LoopController", () => { }, 500); }); - it("should call start the loop when .start() executed", (done) => { + it("should start the loop when .start() executed", (done) => { let callCount = spy.callCount * 1; LoopController.start(); @@ -44,6 +45,10 @@ describe("LoopController", () => { it("should unregister the scrollbar", () => { LoopController.unregisterScrollbar(ScrollbarMock); - expect(LoopController.getRegisteredItems().length).toEqual(0); + expect(LoopController.getRegisteredScrollbars().length).toEqual(0); + }); + + it("createLoopController should return new controller", () => { + expect(createLoopController()).not.toEqual(LoopController); }); });