diff --git a/karma.conf.js b/karma.conf.js index f323a2d4..0a9820dd 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -3,28 +3,40 @@ process.env.NODE_ENV = 'test'; const webpack = require("webpack"); const path = require("path"); -const webpackConfig = require('./webpack.config.js')({isTest: true}); +const webpackConfig = require('./webpack.config.js')({ isTest: true }); delete webpackConfig.entry; +let frameworks = [ + "mocha", + "chai", + "sinon", + "es6-shim" +]; + +let plugins = [ + "karma-webpack", + "karma-sourcemap-writer", + "karma-sourcemap-loader", + "karma-mocha-reporter", + "karma-mocha", + "karma-chai", + "karma-sinon", + "karma-es6-shim", + "karma-remap-istanbul", + "karma-coverage-istanbul-reporter" +]; + module.exports = function(config) { var configuration = { basePath: "", - frameworks: [ - "mocha", - "chai", - "sinon", - "es6-shim" - ], + frameworks: frameworks, files: [ - "./test/**/**/**.test.ts", - { - pattern: '**/*.map', - served: true, - included: false, - watched: true - } + { pattern: "node_modules/reflect-metadata/Reflect.js", include: true }, + { pattern: "node_modules/bluebird/js/browser/bluebird.js", include: true }, + { pattern: "./test/**/**/**.test.ts", include: true }, + { pattern: '**/*.map', served: true, included: false, watched: true } ], preprocessors: { "./**/**/**/**.ts": ["sourcemap"], @@ -34,18 +46,7 @@ module.exports = function(config) { webpackMiddleware: { noInfo: true }, - plugins: [ - "karma-webpack", - "karma-sourcemap-writer", - "karma-sourcemap-loader", - "karma-remap-istanbul", - "karma-mocha-reporter", - "karma-mocha", - "karma-chai", - "karma-sinon", - "karma-es6-shim", - "karma-coverage-istanbul-reporter" - ], + plugins: plugins, reporters: ( config.singleRun ? ["dots", "mocha", "coverage-istanbul"] : @@ -65,7 +66,7 @@ module.exports = function(config) { colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'] + browsers: [] }; if (process.env.TRAVIS) { diff --git a/src/robotlegs/bender/extensions/contextView/impl/ContextViewListenerConfig.ts b/src/robotlegs/bender/extensions/contextView/impl/ContextViewListenerConfig.ts index 82af2b8a..a4c42650 100644 --- a/src/robotlegs/bender/extensions/contextView/impl/ContextViewListenerConfig.ts +++ b/src/robotlegs/bender/extensions/contextView/impl/ContextViewListenerConfig.ts @@ -5,9 +5,7 @@ // in accordance with the terms of the license agreement accompanying it. // ------------------------------------------------------------------------------ -import { injectable, inject } from "inversify"; - -import { IConfig } from "@robotlegsjs/core"; +import { injectable, inject, IConfig } from "@robotlegsjs/core"; import { IContextView } from "../api/IContextView"; import { IViewManager } from "../../viewManager/api/IViewManager"; diff --git a/src/robotlegs/bender/extensions/mediatorMap/impl/Mediator.ts b/src/robotlegs/bender/extensions/mediatorMap/impl/Mediator.ts index b6e5b23e..56c54c7a 100644 --- a/src/robotlegs/bender/extensions/mediatorMap/impl/Mediator.ts +++ b/src/robotlegs/bender/extensions/mediatorMap/impl/Mediator.ts @@ -5,9 +5,13 @@ // in accordance with the terms of the license agreement accompanying it. // ------------------------------------------------------------------------------ -import { injectable, inject } from "inversify"; - -import { IEventMap, IEventDispatcher, Event } from "@robotlegsjs/core"; +import { + injectable, + inject, + IEventMap, + IEventDispatcher, + Event +} from "@robotlegsjs/core"; import { IMediator } from "../api/IMediator"; diff --git a/src/robotlegs/bender/extensions/mediatorMap/impl/MediatorMap.ts b/src/robotlegs/bender/extensions/mediatorMap/impl/MediatorMap.ts index b2fcdb26..fef65101 100644 --- a/src/robotlegs/bender/extensions/mediatorMap/impl/MediatorMap.ts +++ b/src/robotlegs/bender/extensions/mediatorMap/impl/MediatorMap.ts @@ -6,6 +6,8 @@ // ------------------------------------------------------------------------------ import { + injectable, + inject, IContext, ILogger, ITypeMatcher, @@ -23,8 +25,6 @@ import { MediatorViewHandler } from "./MediatorViewHandler"; import { NullMediatorUnmapper } from "./NullMediatorUnmapper"; import { MediatorMapper } from "./MediatorMapper"; -import { injectable, inject } from "inversify"; - /** * @private */ diff --git a/src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding.ts b/src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding.ts index 79ce2ad6..28281147 100644 --- a/src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding.ts +++ b/src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding.ts @@ -100,7 +100,7 @@ export class ContainerBinding extends EventDispatcher { public handleView(view: any, type: FunctionConstructor): void { let length: number = this._handlers.length; for (let i: number = 0; i < length; i++) { - let handler: IViewHandler = this._handlers[i]; + let handler: IViewHandler = this._handlers[i]; handler.handleView(view, type); } } diff --git a/src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent.ts b/src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent.ts index 38d9cdc8..2328f002 100644 --- a/src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent.ts +++ b/src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent.ts @@ -5,6 +5,8 @@ // in accordance with the terms of the license agreement accompanying it. // ------------------------------------------------------------------------------ +import { Event } from "@robotlegsjs/core"; + /** * @private */ @@ -33,7 +35,7 @@ export class ContainerBindingEvent extends Event { /** * @inheritDoc */ - public clone(): Event { + public clone(): ContainerBindingEvent { return new ContainerBindingEvent(this.type); } } diff --git a/src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry.ts b/src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry.ts index da9218cd..3754d8dc 100644 --- a/src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry.ts +++ b/src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry.ts @@ -7,6 +7,8 @@ import { EventDispatcher } from "@robotlegsjs/core"; +import { contains } from "./contains"; + import { ContainerBinding } from "./ContainerBinding"; import { ContainerBindingEvent } from "./ContainerBindingEvent"; import { ContainerRegistryEvent } from "./ContainerRegistryEvent"; @@ -60,10 +62,10 @@ export class ContainerRegistry extends EventDispatcher { */ public addContainer(container: any): ContainerBinding { let binding = this._bindingByContainer.get(container); - if (binding) return binding; - - binding = this.createBinding(container); - this._bindingByContainer.set(container, binding); + if (!binding) { + binding = this.createBinding(container); + this._bindingByContainer.set(container, binding); + } return binding; } @@ -71,9 +73,7 @@ export class ContainerRegistry extends EventDispatcher { * @private */ public removeContainer(container: any): ContainerBinding { - const binding: ContainerBinding = this._bindingByContainer.get( - container - ); + let binding: ContainerBinding = this._bindingByContainer.get(container); if (binding) { this.removeBinding(binding); @@ -90,7 +90,7 @@ export class ContainerRegistry extends EventDispatcher { public findParentBinding(target: any): ContainerBinding { let parent: any = target.parent; while (parent) { - const binding: ContainerBinding = this._bindingByContainer.get( + let binding: ContainerBinding = this._bindingByContainer.get( parent ); if (binding) { @@ -119,7 +119,7 @@ export class ContainerRegistry extends EventDispatcher { // Add a listener so that we can remove this binding when it has no handlers binding.addEventListener( ContainerBindingEvent.BINDING_EMPTY, - this.onBindingEmpty + this.onBindingEmpty.bind(this) ); // If the new binding doesn't have a parent it is a Root @@ -132,11 +132,13 @@ export class ContainerRegistry extends EventDispatcher { // A. Don't have a parent, OR // B. Have a parent that is not contained within the new binding this._bindingByContainer.forEach(childBinding => { - if (container.contains(childBinding.container)) { + if (contains(container, childBinding.container)) { if (!childBinding.parent) { this.removeRootBinding(childBinding); childBinding.parent = binding; - } else if (!container.contains(childBinding.parent.container)) { + } else if ( + !contains(container, childBinding.parent.container) + ) { childBinding.parent = binding; } } diff --git a/src/robotlegs/bender/extensions/viewManager/impl/ViewManager.ts b/src/robotlegs/bender/extensions/viewManager/impl/ViewManager.ts index 6a98dc06..c1e18a8c 100644 --- a/src/robotlegs/bender/extensions/viewManager/impl/ViewManager.ts +++ b/src/robotlegs/bender/extensions/viewManager/impl/ViewManager.ts @@ -5,9 +5,9 @@ // in accordance with the terms of the license agreement accompanying it. // ------------------------------------------------------------------------------ -import { injectable } from "inversify"; +import { injectable, EventDispatcher } from "@robotlegsjs/core"; -import { EventDispatcher } from "@robotlegsjs/core"; +import { contains } from "./contains"; import { IViewHandler } from "../api/IViewHandler"; import { IViewManager } from "../api/IViewManager"; @@ -73,11 +73,10 @@ export class ViewManager extends EventDispatcher implements IViewManager { } this._containers.push(container); - - for (let i in this._handlers) { - let handler: IViewHandler = this._handlers[i]; + this._handlers.forEach(handler => { this._registry.addContainer(container).addHandler(handler); - } + }); + this.dispatchEvent( new ViewManagerEvent(ViewManagerEvent.CONTAINER_ADD, container) ); @@ -88,6 +87,7 @@ export class ViewManager extends EventDispatcher implements IViewManager { */ public removeContainer(container: any): void { let index: number = this._containers.indexOf(container); + if (index === -1) { return; } @@ -95,10 +95,11 @@ export class ViewManager extends EventDispatcher implements IViewManager { this._containers.splice(index, 1); let binding: ContainerBinding = this._registry.getBinding(container); - for (let i in this._handlers) { - let handler: IViewHandler = this._handlers[i]; + + this._handlers.forEach(handler => { binding.removeHandler(handler); - } + }); + this.dispatchEvent( new ViewManagerEvent(ViewManagerEvent.CONTAINER_REMOVE, container) ); @@ -113,11 +114,9 @@ export class ViewManager extends EventDispatcher implements IViewManager { } this._handlers.push(handler); - - for (let i in this._containers) { - let container: any = this._containers[i]; + this._containers.forEach(container => { this._registry.addContainer(container).addHandler(handler); - } + }); this.dispatchEvent( new ViewManagerEvent(ViewManagerEvent.HANDLER_ADD, null, handler) @@ -136,10 +135,9 @@ export class ViewManager extends EventDispatcher implements IViewManager { this._handlers.splice(index, 1); - for (let i in this._containers) { - let container: any = this._containers[i]; + this._containers.forEach(container => { this._registry.getBinding(container).removeHandler(handler); - } + }); this.dispatchEvent( new ViewManagerEvent(ViewManagerEvent.HANDLER_REMOVE, null, handler) @@ -150,16 +148,13 @@ export class ViewManager extends EventDispatcher implements IViewManager { * @inheritDoc */ public removeAllHandlers(): void { - for (let i in this._containers) { - let container: any = this._containers[i]; - var binding: ContainerBinding = this._registry.getBinding( - container - ); - for (let j in this._handlers) { - let handler: IViewHandler = this._handlers[j]; + let binding: ContainerBinding = null; + this._containers.forEach(container => { + binding = this._registry.getBinding(container); + this._handlers.forEach(handler => { binding.removeHandler(handler); - } - } + }); + }); } /*============================================================================*/ @@ -167,19 +162,19 @@ export class ViewManager extends EventDispatcher implements IViewManager { /*============================================================================*/ private validContainer(container: any): boolean { - for (let i in this._containers) { - let registeredContainer: any = this._containers[i]; + this._containers.forEach(registeredContainer => { if (container === registeredContainer) { return false; } if ( - registeredContainer.contains(container) || - container.contains(registeredContainer) + contains(registeredContainer, container) || + contains(container, registeredContainer) ) { throw new Error("Containers can not be nested"); } - } + }); + return true; } } diff --git a/src/robotlegs/bender/extensions/viewManager/impl/contains.ts b/src/robotlegs/bender/extensions/viewManager/impl/contains.ts new file mode 100644 index 00000000..dfa510b4 --- /dev/null +++ b/src/robotlegs/bender/extensions/viewManager/impl/contains.ts @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +/*============================================================================*/ +/* Public Functions */ +/*============================================================================*/ + +/** + * Determines whether the specified child object is a child of the containter instance or the instance itself. + * The search includes the entire display list including the containter instance. + * Grandchildren, great-grandchildren, and so on each return true. + * + * @param container The container. + * @param child The child object to test. + * + * @return true if the child object is a child of the container or the container itself; otherwise false. + */ +export function contains(container: any, child: any): boolean { + let found: boolean = false; + if (container === child || container.children.indexOf(child) >= 0) { + found = true; + } else { + for (let c of container.children) { + if (this.contains(c, child)) { + found = true; + break; + } + } + } + return found; +} diff --git a/test/entry.ts b/test/entry.ts index 89b2f603..680ee335 100644 --- a/test/entry.ts +++ b/test/entry.ts @@ -1,4 +1,5 @@ -import "reflect-metadata"; +/// + import "bluebird/js/browser/bluebird"; import "es6-symbol/implement"; import "es6-map/implement"; diff --git a/test/robotlegs/bender/extensions/mediatorMap/mediatorMapExtension.test.ts b/test/robotlegs/bender/extensions/mediatorMap/mediatorMapExtension.test.ts new file mode 100644 index 00000000..bb7749c2 --- /dev/null +++ b/test/robotlegs/bender/extensions/mediatorMap/mediatorMapExtension.test.ts @@ -0,0 +1,60 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../entry"; + +import { assert } from "chai"; + +import { IContext, Context } from "@robotlegsjs/core"; + +import { + ViewManagerExtension, + MediatorMapExtension, + IMediatorMap +} from "../../../../../src"; + +import { MediatorMap } from "../../../../../src/robotlegs/bender/extensions/mediatorMap/impl/MediatorMap"; + +describe("MediatorMapExtension", () => { + let context: IContext; + + beforeEach(() => { + context = new Context(); + }); + + afterEach(() => { + context = null; + }); + + it("installing_after_initialization_throws_error", () => { + function installExtensionAfterInitialization(): void { + context.initialize(); + context.install(MediatorMapExtension); + } + assert.throws(installExtensionAfterInitialization, Error); + }); + + it("mediatorMap_is_mapped_into_injector_on_initialize", () => { + let mediatorMap: IMediatorMap = null; + context.install(ViewManagerExtension, MediatorMapExtension); + context.whenInitializing(function(): void { + mediatorMap = context.injector.get(IMediatorMap); + }); + context.initialize(); + assert.isNotNull(mediatorMap); + assert.instanceOf(mediatorMap, MediatorMap); + }); + + it("mediatorMap_is_unmapped_from_injector_on_destroy", () => { + context.install(ViewManagerExtension, MediatorMapExtension); + context.afterDestroying(function(): void { + assert.isFalse(context.injector.isBound(IMediatorMap)); + }); + context.initialize(); + context.destroy(); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/containerBinding.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/containerBinding.test.ts new file mode 100644 index 00000000..354e2a16 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/containerBinding.test.ts @@ -0,0 +1,169 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { Sprite } from "pixi.js"; + +import { IViewHandler } from "../../../../../../src/robotlegs/bender/extensions/viewManager/api/IViewHandler"; +import { ContainerBinding } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding"; +import { ContainerBindingEvent } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent"; + +import { CallbackViewHandler } from "../support/CallbackViewHandler"; + +describe("ContainerBinding", () => { + let container: Sprite = null; + let binding: ContainerBinding = null; + + beforeEach(() => { + container = new Sprite(); + binding = new ContainerBinding(container); + }); + + afterEach(() => { + container = null; + binding = null; + }); + + it("container_is_stored", () => { + assert.equal(binding.container, container); + }); + + it("parent_is_stored", () => { + let parentContainer: Sprite = new Sprite(); + let parentBinding = new ContainerBinding(parentContainer); + binding.parent = parentBinding; + assert.equal(binding.parent, parentBinding); + }); + + it("handler_is_invoked", () => { + let callCount: number = 0; + binding.addHandler( + new CallbackViewHandler(function(view: any, type: any): void { + callCount++; + }) + ); + binding.handleView(container, Sprite.constructor); + assert.equal(callCount, 1); + }); + + it("handler_is_passed_correct_details", () => { + const expectedView: Sprite = container; + const expectedType: FunctionConstructor = Sprite.constructor; + let actualView: Sprite = null; + let actualType: FunctionConstructor = null; + binding.addHandler( + new CallbackViewHandler(function(view: any, type: any): void { + actualView = view; + actualType = type; + }) + ); + binding.handleView(expectedView, expectedType); + assert.equal(actualView, expectedView); + assert.equal(actualType, expectedType); + }); + + it("handler_is_not_invoked_after_removal", () => { + let callCount: number = 0; + const handler: IViewHandler = new CallbackViewHandler(function( + view: any, + type: any + ): void { + callCount++; + }); + binding.addHandler(handler); + binding.removeHandler(handler); + binding.handleView(container, Sprite.constructor); + assert.equal(callCount, 0); + }); + + it("handler_is_not_invoked_multiple_times_when_added_multiple_times", () => { + let callCount: number = 0; + const handler: IViewHandler = new CallbackViewHandler(function( + view: any, + type: any + ): void { + callCount++; + }); + binding.addHandler(handler); + binding.addHandler(handler); + binding.addHandler(handler); + binding.handleView(container, Sprite.constructor); + assert.equal(callCount, 1); + }); + + it("handlers_are_invoked_in_order", () => { + const expected: string[] = ["handler1", "handler2", "handler3"]; + let actual: string[] = []; + binding.addHandler( + new CallbackViewHandler(function(view: any, type: any): void { + actual.push("handler1"); + }) + ); + binding.addHandler( + new CallbackViewHandler(function(view: any, type: any): void { + actual.push("handler2"); + }) + ); + binding.addHandler( + new CallbackViewHandler(function(view: any, type: any): void { + actual.push("handler3"); + }) + ); + binding.handleView(container, Sprite.constructor); + assert.deepEqual(actual, expected); + }); + + it("binding_fires_event_on_empty", () => { + const handler: IViewHandler = new CallbackViewHandler(); + let callCount: number = 0; + binding.addEventListener(ContainerBindingEvent.BINDING_EMPTY, function( + event: ContainerBindingEvent + ): void { + callCount++; + }); + binding.addHandler(handler); + binding.removeHandler(handler); + assert.equal(callCount, 1); + }); + + it("event_on_empty_is_not_invoked_multiple_times_when_handler_is_removed_multiple_times", () => { + const handler: IViewHandler = new CallbackViewHandler(); + let callCount: number = 0; + binding.addEventListener(ContainerBindingEvent.BINDING_EMPTY, function( + event: ContainerBindingEvent + ): void { + callCount++; + }); + binding.addHandler(handler); + binding.removeHandler(handler); + binding.removeHandler(handler); + binding.removeHandler(handler); + assert.equal(callCount, 1); + }); + + it("binding_event_on_empty_fired_once_when_more_than_one_handler_are_added", () => { + const handler1: IViewHandler = new CallbackViewHandler(); + const handler2: IViewHandler = new CallbackViewHandler(); + const handler3: IViewHandler = new CallbackViewHandler(); + let callCount: number = 0; + binding.addEventListener(ContainerBindingEvent.BINDING_EMPTY, function( + event: ContainerBindingEvent + ): void { + callCount++; + }); + binding.addHandler(handler1); + binding.addHandler(handler2); + binding.addHandler(handler3); + binding.removeHandler(handler1); + binding.removeHandler(handler2); + binding.removeHandler(handler3); + assert.equal(callCount, 1); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/containerBindingEvent.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/containerBindingEvent.test.ts new file mode 100644 index 00000000..34dbde78 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/containerBindingEvent.test.ts @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { ContainerBindingEvent } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerBindingEvent"; + +describe("ContainerBindingEvent", () => { + let event: ContainerBindingEvent = null; + + beforeEach(() => { + event = new ContainerBindingEvent(ContainerBindingEvent.BINDING_EMPTY); + }); + + afterEach(() => { + event = null; + }); + + it("ensure_static_properties_will_not_change", () => { + assert.equal(ContainerBindingEvent.BINDING_EMPTY, "bindingEmpty"); + }); + + it("type_is_stored", () => { + assert.equal(event.type, ContainerBindingEvent.BINDING_EMPTY); + }); + + it("event_is_cloned", () => { + let clone: ContainerBindingEvent = event.clone(); + assert.equal(clone.type, event.type); + assert.notEqual(clone, event); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/containerRegistry.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/containerRegistry.test.ts new file mode 100644 index 00000000..b992cbb3 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/containerRegistry.test.ts @@ -0,0 +1,404 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { Sprite } from "pixi.js"; + +import { IViewHandler } from "../../../../../../src/robotlegs/bender/extensions/viewManager/api/IViewHandler"; +import { ContainerBinding } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerBinding"; +import { ContainerRegistry } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry"; +import { ContainerRegistryEvent } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistryEvent"; + +import { CallbackViewHandler } from "../support/CallbackViewHandler"; +import { TreeContainer } from "../support/TreeContainer"; + +describe("ContainerRegistry", () => { + let registry: ContainerRegistry = null; + + beforeEach(() => { + registry = new ContainerRegistry(); + }); + + afterEach(() => { + registry = null; + }); + + it("add_container", () => { + let container: Sprite = new Sprite(); + let containerBinding: ContainerBinding = registry.addContainer( + container + ); + assert.equal(containerBinding.container, container); + }); + + it("add_twice_same_container", () => { + let container: Sprite = new Sprite(); + let containerBinding1: ContainerBinding = registry.addContainer( + container + ); + let containerBinding2: ContainerBinding = registry.addContainer( + container + ); + assert.equal(containerBinding1.container, container); + assert.equal(containerBinding2.container, container); + assert.equal(containerBinding1, containerBinding2); + }); + + it("get_bindings", () => { + let container1: Sprite = new Sprite(); + let container2: Sprite = new Sprite(); + let container3: Sprite = new Sprite(); + + let containerBinding1: ContainerBinding = registry.addContainer( + container1 + ); + let containerBinding2: ContainerBinding = registry.addContainer( + container2 + ); + let containerBinding3: ContainerBinding = registry.addContainer( + container3 + ); + + let expectedBindings: ContainerBinding[] = [ + containerBinding1, + containerBinding2, + containerBinding3 + ]; + + assert.deepEqual(expectedBindings, registry.bindings); + }); + + it("finds_correct_nearest_interested_container_view_and_returns_its_binding", () => { + let searchTrees: TreeContainer[] = createTrees(3, 3); + + for (let searchTree of searchTrees) { + registry.addContainer(searchTree); + } + + let correctTree: TreeContainer; + let result: ContainerBinding; + + for (correctTree of searchTrees) { + for (let treeChild of correctTree.treeChildren) { + result = registry.findParentBinding(treeChild); + assert.equal(result.container, correctTree); + + for (let treeGrandchild of treeChild.treeChildren) { + result = registry.findParentBinding(treeGrandchild); + assert.equal(result.container, correctTree); + + for (let treeGreatGrandchild of treeGrandchild.treeChildren) { + result = registry.findParentBinding( + treeGreatGrandchild + ); + assert.equal(result.container, correctTree); + } + } + } + } + }); + + it("binding_returns_with_correct_interested_parent_chain", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + + registry.addContainer(searchTrees[0]); + registry.addContainer(searchTrees[1]); + registry.addContainer(searchTrees[1].treeChildren[3]); + + let searchItem: Sprite = + searchTrees[1].treeChildren[3].treeChildren[3].treeChildren[3] + .treeChildren[3]; + let result: ContainerBinding = registry.findParentBinding(searchItem); + + assert.equal( + searchTrees[1].treeChildren[3], + result.container, + "Binding returns with correct container view" + ); + assert.equal( + searchTrees[1], + result.parent.container, + "Binding returns with correct container parent view" + ); + assert.equal(null, result.parent.parent, "Further parents are null"); + }); + + it("binding_returns_with_correct_interested_parent_chain_if_interested_views_added_in_wrong_order", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + + registry.addContainer(searchTrees[0]); + registry.addContainer(searchTrees[1].treeChildren[3]); + registry.addContainer(searchTrees[1]); + + let searchItem: Sprite = + searchTrees[1].treeChildren[3].treeChildren[3].treeChildren[3] + .treeChildren[3]; + let result: ContainerBinding = registry.findParentBinding(searchItem); + + assert.equal( + searchTrees[1].treeChildren[3], + result.container, + "Binding returns with correct container view" + ); + + assert.equal( + searchTrees[1], + result.parent.container, + "Binding returns with correct container parent view" + ); + assert.equal(null, result.parent.parent, "Further parents are null"); + }); + + it("binding_returns_with_correct_interested_parent_chain_if_interested_views_added_in_wrong_order_with_gaps", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + + registry.addContainer(searchTrees[0]); + registry.addContainer(searchTrees[1].treeChildren[3].treeChildren[2]); + registry.addContainer(searchTrees[1]); + + let searchItem: Sprite = + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + .treeChildren[3]; + let result: ContainerBinding = registry.findParentBinding(searchItem); + + assert.equal( + searchTrees[1].treeChildren[3].treeChildren[2], + result.container, + "Binding returns with correct container view" + ); + + assert.equal( + searchTrees[1], + result.parent.container, + "Binding returns with correct container parent view" + ); + assert.equal(null, result.parent.parent, "Further parents are null"); + }); + + it("binding_returns_with_correct_interested_parent_chain_after_removal", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + + registry.addContainer(searchTrees[0]); + registry.addContainer(searchTrees[1]); + registry.addContainer( + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + ); + registry.addContainer(searchTrees[1].treeChildren[3].treeChildren[2]); + registry.addContainer(searchTrees[1].treeChildren[3]); + + registry.removeContainer( + searchTrees[1].treeChildren[3].treeChildren[2] + ); + + let searchItem: Sprite = + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + .treeChildren[3]; + let result: ContainerBinding = registry.findParentBinding(searchItem); + + assert.equal( + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3], + result.container, + "Binding returns with correct container view" + ); + assert.equal( + searchTrees[1].treeChildren[3], + result.parent.container, + "Binding returns with correct container parent view" + ); + assert.equal( + searchTrees[1], + result.parent.parent.container, + "Binding returns with correct container parent parent view" + ); + assert.equal( + null, + result.parent.parent.parent, + "Further parents are null" + ); + }); + + it("returns_null_if_search_item_is_not_inside_an_included_view", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + + registry.addContainer(searchTrees[0]); + registry.addContainer(searchTrees[1]); + registry.addContainer( + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + ); + registry.addContainer(searchTrees[1].treeChildren[3].treeChildren[2]); + registry.addContainer(searchTrees[1].treeChildren[3]); + + registry.removeContainer( + searchTrees[1].treeChildren[3].treeChildren[2] + ); + + let searchItem: Sprite = + searchTrees[2].treeChildren[3].treeChildren[2].treeChildren[3] + .treeChildren[3]; + let result: ContainerBinding = registry.findParentBinding(searchItem); + + assert.equal( + null, + result, + "Returns null if not inside an included view" + ); + }); + + it("returns_root_container_view_bindings_one_item", () => { + let searchTrees: TreeContainer[] = createTrees(1, 1); + let expectedBinding: ContainerBinding = registry.addContainer( + searchTrees[0] + ); + let expectedRootBindings: ContainerBinding[] = [expectedBinding]; + assert.deepEqual( + expectedRootBindings, + registry.rootBindings, + "Returns root container view bindings one item" + ); + }); + + it("returns_root_container_view_bindings_many_items", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + let firstExpectedBinding: ContainerBinding = registry.addContainer( + searchTrees[0] + ); + + registry.addContainer( + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + ); + registry.addContainer(searchTrees[1].treeChildren[3].treeChildren[2]); + + let secondExpectedBinding: ContainerBinding = registry.addContainer( + searchTrees[1] + ); + + registry.addContainer(searchTrees[1].treeChildren[3]); + + let expectedRootBindings: ContainerBinding[] = [ + firstExpectedBinding, + secondExpectedBinding + ]; + assert.deepEqual( + expectedRootBindings, + registry.rootBindings, + "Returns root container view bindings many items" + ); + }); + + it("returns_root_container_view_bindings_many_items_after_removals", () => { + let searchTrees: TreeContainer[] = createTrees(5, 4); + let firstExpectedBinding: ContainerBinding = registry.addContainer( + searchTrees[0] + ); + + registry.addContainer( + searchTrees[1].treeChildren[3].treeChildren[2].treeChildren[3] + ); + registry.addContainer(searchTrees[1].treeChildren[3].treeChildren[2]); + registry.addContainer(searchTrees[1]); + + let secondExpectedBinding: ContainerBinding = registry.addContainer( + searchTrees[1].treeChildren[3] + ); + + registry.removeContainer(searchTrees[1]); + + let expectedRootBindings: ContainerBinding[] = [ + firstExpectedBinding, + secondExpectedBinding + ]; + assert.deepEqual( + expectedRootBindings, + registry.rootBindings, + "Returns root container view bindings many items after removals" + ); + }); + + it("adding_container_dispatches_event", () => { + let container: Sprite = new Sprite(); + let callCount: number = 0; + registry.addEventListener( + ContainerRegistryEvent.CONTAINER_ADD, + function onContainerAdd(event: ContainerRegistryEvent): void { + callCount++; + } + ); + registry.addContainer(container); + registry.addContainer(container); + assert.equal(callCount, 1); + }); + + it("removing_container_dispatches_event", () => { + let container: Sprite = new Sprite(); + let callCount: number = 0; + registry.addEventListener( + ContainerRegistryEvent.CONTAINER_REMOVE, + function onContainerRemove(event: ContainerRegistryEvent): void { + callCount++; + } + ); + registry.addContainer(container); + registry.removeContainer(container); + registry.removeContainer(container); + assert.equal(callCount, 1); + }); + + it("adding_root_container_dispatches_event", () => { + let container: Sprite = new Sprite(); + let callCount: number = 0; + registry.addEventListener( + ContainerRegistryEvent.ROOT_CONTAINER_ADD, + function onRootContainerAdd(event: ContainerRegistryEvent): void { + callCount++; + } + ); + registry.addContainer(container); + assert.equal(callCount, 1); + }); + + it("removing_root_container_dispatches_event", () => { + let container: Sprite = new Sprite(); + let callCount: number = 0; + registry.addEventListener( + ContainerRegistryEvent.ROOT_CONTAINER_REMOVE, + function onRootContainerRemove( + event: ContainerRegistryEvent + ): void { + callCount++; + } + ); + registry.addContainer(container); + registry.removeContainer(container); + assert.equal(callCount, 1); + }); + + it("empty_binding_is_removed", () => { + let container: Sprite = new Sprite(); + let handler: IViewHandler = new CallbackViewHandler(); + registry.addContainer(container).addHandler(handler); + registry.getBinding(container).removeHandler(handler); + assert.isUndefined(registry.getBinding(container)); + }); + + function createTrees( + treeDepth: number, + treeWidth: number + ): TreeContainer[] { + const trees: TreeContainer[] = []; + for (let i: number = 0; i < treeWidth; i++) { + let treeContainer: TreeContainer = new TreeContainer( + treeDepth, + treeWidth + ); + trees.push(treeContainer); + } + return trees; + } +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/containerRegistryEvent.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/containerRegistryEvent.test.ts new file mode 100644 index 00000000..56ae3b3d --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/containerRegistryEvent.test.ts @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { Sprite } from "pixi.js"; + +import { ContainerRegistryEvent } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistryEvent"; + +describe("ContainerRegistryEvent", () => { + let container: Sprite = null; + let event: ContainerRegistryEvent = null; + + beforeEach(() => { + container = new Sprite(); + event = new ContainerRegistryEvent( + ContainerRegistryEvent.CONTAINER_ADD, + container + ); + }); + + afterEach(() => { + container = null; + event = null; + }); + + it("ensure_static_properties_will_not_change", () => { + assert.equal(ContainerRegistryEvent.CONTAINER_ADD, "containerAdd"); + assert.equal( + ContainerRegistryEvent.CONTAINER_REMOVE, + "containerRemove" + ); + assert.equal( + ContainerRegistryEvent.ROOT_CONTAINER_ADD, + "rootContainerAdd" + ); + assert.equal( + ContainerRegistryEvent.ROOT_CONTAINER_REMOVE, + "rootContainerRemove" + ); + }); + + it("type_is_stored", () => { + assert.equal(event.type, ContainerRegistryEvent.CONTAINER_ADD); + }); + + it("container_is_stored", () => { + assert.equal(event.container, container); + }); + + it("event_is_cloned", () => { + let clone: ContainerRegistryEvent = event.clone(); + assert.equal(clone.type, event.type); + assert.equal(clone.container, event.container); + assert.notEqual(clone, event); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/viewManager.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/viewManager.test.ts new file mode 100644 index 00000000..fb05f4cd --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/viewManager.test.ts @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { Sprite } from "pixi.js"; + +import { ContainerRegistry } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ContainerRegistry"; +import { ViewManager } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ViewManager"; + +import { CallbackViewHandler } from "../support/CallbackViewHandler"; + +describe("ViewManager", () => { + let container: Sprite = null; + let registry: ContainerRegistry = null; + let viewManager: ViewManager = null; + + beforeEach(() => { + container = new Sprite(); + registry = new ContainerRegistry(); + viewManager = new ViewManager(registry); + }); + + afterEach(() => { + container = null; + registry = null; + viewManager = null; + }); + + it("container_is_added", () => { + viewManager.addContainer(container); + }); + + it("container_is_stored", () => { + let expectedContainers: any[] = [container]; + viewManager.addContainer(container); + assert.deepEqual(viewManager.containers, expectedContainers); + }); + + it("addContainer_throws_if_containers_are_nested_case1", () => { + function addNestedContainers(): void { + const container1: Sprite = new Sprite(); + const container2: Sprite = new Sprite(); + container1.addChild(container2); + viewManager.addContainer(container1); + viewManager.addContainer(container2); + } + assert.throws(addNestedContainers, Error); + }); + + it("addContainer_throws_if_containers_are_nested_case2", () => { + function addNestedContainers(): void { + const container1: Sprite = new Sprite(); + const container2: Sprite = new Sprite(); + container2.addChild(container1); + viewManager.addContainer(container1); + viewManager.addContainer(container2); + } + assert.throws(addNestedContainers, Error); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/impl/viewManagerEvent.test.ts b/test/robotlegs/bender/extensions/viewManager/impl/viewManagerEvent.test.ts new file mode 100644 index 00000000..3fb2c97f --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/impl/viewManagerEvent.test.ts @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../../entry"; + +import { assert } from "chai"; + +import { Sprite } from "pixi.js"; + +import { IViewHandler } from "../../../../../../src/robotlegs/bender/extensions/viewManager/api/IViewHandler"; +import { ViewManagerEvent } from "../../../../../../src/robotlegs/bender/extensions/viewManager/impl/ViewManagerEvent"; + +import { CallbackViewHandler } from "../support/CallbackViewHandler"; + +describe("ViewManagerEvent", () => { + let container: Sprite = null; + let handler: IViewHandler = null; + let event: ViewManagerEvent = null; + + beforeEach(() => { + container = new Sprite(); + handler = new CallbackViewHandler(); + event = new ViewManagerEvent( + ViewManagerEvent.CONTAINER_ADD, + container, + handler + ); + }); + + afterEach(() => { + container = null; + handler = null; + event = null; + }); + + it("ensure_static_properties_will_not_change", () => { + assert.equal(ViewManagerEvent.CONTAINER_ADD, "containerAdd"); + assert.equal(ViewManagerEvent.CONTAINER_REMOVE, "containerRemove"); + assert.equal(ViewManagerEvent.HANDLER_ADD, "handlerAdd"); + assert.equal(ViewManagerEvent.HANDLER_REMOVE, "handlerRemove"); + }); + + it("type_is_stored", () => { + assert.equal(event.type, ViewManagerEvent.CONTAINER_ADD); + }); + + it("container_is_stored", () => { + assert.equal(event.container, container); + }); + + it("handler_is_stored", () => { + assert.equal(event.handler, handler); + }); + + it("event_is_cloned", () => { + let clone: ViewManagerEvent = event.clone(); + assert.equal(clone.type, event.type); + assert.equal(clone.container, event.container); + assert.equal(clone.handler, event.handler); + assert.notEqual(clone, event); + }); +}); diff --git a/test/robotlegs/bender/extensions/viewManager/support/CallbackViewHandler.ts b/test/robotlegs/bender/extensions/viewManager/support/CallbackViewHandler.ts new file mode 100644 index 00000000..a3f635b7 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/support/CallbackViewHandler.ts @@ -0,0 +1,25 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import { IViewHandler } from "../../../../../../src/robotlegs/bender/extensions/viewManager/api/IViewHandler"; + +/** + * @private + */ +export class CallbackViewHandler implements IViewHandler { + private _callback: Function; + + constructor(callback: Function = null) { + this._callback = callback; + } + + public handleView(view: any, type: any): void { + if (this._callback) { + this._callback(view, type); + } + } +} diff --git a/test/robotlegs/bender/extensions/viewManager/support/TreeContainer.ts b/test/robotlegs/bender/extensions/viewManager/support/TreeContainer.ts new file mode 100644 index 00000000..4b92da31 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/support/TreeContainer.ts @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import { Sprite } from "pixi.js"; + +/** + * @private + */ +export class TreeContainer extends Sprite { + private _treeDepth: number = 0; + private _treeWidth: number = 0; + private _treeChildren: TreeContainer[] = []; + + constructor(treeDetpth: number, treeWidth: number) { + super(); + + this._treeDepth = treeDetpth; + this._treeWidth = treeWidth; + + this.populate(); + } + + private populate(): void { + if (this._treeDepth > 0) { + for (let i: number = 0; i < this._treeWidth; i++) { + let child: TreeContainer = new TreeContainer( + this._treeDepth - 1, + this._treeWidth + ); + this._treeChildren.push(child); + this.addChild(child); + } + } + } + + public get treeChildren(): TreeContainer[] { + return this._treeChildren; + } +} diff --git a/test/robotlegs/bender/extensions/viewManager/viewManagerExtension.test.ts b/test/robotlegs/bender/extensions/viewManager/viewManagerExtension.test.ts new file mode 100644 index 00000000..9798c3e8 --- /dev/null +++ b/test/robotlegs/bender/extensions/viewManager/viewManagerExtension.test.ts @@ -0,0 +1,47 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) 2017 RobotlegsJS. All Rights Reserved. +// +// NOTICE: You are permitted to use, modify, and distribute this file +// in accordance with the terms of the license agreement accompanying it. +// ------------------------------------------------------------------------------ + +import "../../../../entry"; + +import { assert } from "chai"; + +import { IContext, Context } from "@robotlegsjs/core"; + +import { ViewManagerExtension, IViewManager } from "../../../../../src"; + +import { ViewManager } from "../../../../../src/robotlegs/bender/extensions/viewManager/impl/ViewManager"; + +describe("ViewManagerExtension", () => { + let context: IContext; + + beforeEach(() => { + context = new Context(); + }); + + afterEach(() => { + context = null; + }); + + it("installing after initialization throws error", () => { + function installExtensionAfterInitialization(): void { + context.initialize(); + context.install(ViewManagerExtension); + } + assert.throws(installExtensionAfterInitialization, Error); + }); + + it("viewManager is mapped into injector", () => { + let viewManager: IViewManager = null; + context.install(ViewManagerExtension); + context.whenInitializing(function(): void { + viewManager = context.injector.get(IViewManager); + }); + context.initialize(); + assert.isNotNull(viewManager); + assert.instanceOf(viewManager, ViewManager); + }); +});