From 5a41053f749420f288cfbe63cc5b103f8bc8525a Mon Sep 17 00:00:00 2001 From: Oscar Lorentzon Date: Thu, 3 Aug 2017 11:27:39 +0000 Subject: [PATCH] test(tag): tag scene unit tests --- spec/component/tag/TagScene.spec.ts | 346 ++++++++++++++++++++++++++++ src/component/tag/TagScene.ts | 2 +- 2 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 spec/component/tag/TagScene.spec.ts diff --git a/spec/component/tag/TagScene.spec.ts b/spec/component/tag/TagScene.spec.ts new file mode 100644 index 00000000..5e9598f7 --- /dev/null +++ b/spec/component/tag/TagScene.spec.ts @@ -0,0 +1,346 @@ +/// + +import * as THREE from "three"; +import * as vd from "virtual-dom"; + +import { + RectGeometry, + RenderTag, + Tag, + TagScene, +} from "../../../src/Component"; +import {ISize} from "../../../src/Render"; +import {ISpriteAtlas} from "../../../src/Viewer"; + +describe("TagScene.ctor", () => { + it("should be defined", () => { + const tagScene: TagScene = new TagScene(); + + expect(tagScene).toBeDefined(); + }); + + it("should not need render after being created", () => { + const tagScene: TagScene = new TagScene(); + + expect(tagScene.needsRender).toBe(false); + }); +}); + +class TestTag extends Tag { + private _testProp: number = 0; + + public get testProp(): number { + return this._testProp; + } + + public set testProp(value: number) { + this._testProp = value; + this._notifyChanged$.next(this); + } + } + +class TestRenderTag extends RenderTag { + public dispose(): void { /*noop*/ } + public getDOMObjects(atlas: ISpriteAtlas, camera: THREE.Camera, size: ISize): vd.VNode[] { return []; } + public getGLObjects(): THREE.Object3D[] { return []; } + public getRetrievableObjects(): THREE.Object3D[] { return []; } +} + +describe("TagScene.add", () => { + it("should add a single render tag", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + + const result: RenderTag = tagScene.get(renderTag.tag.id); + + expect(result).toBeDefined(); + expect(result.tag.id).toBe(renderTag.tag.id); + expect(result).toBe(renderTag); + }); + + it("should add multiple render tags", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag1: TestTag = new TestTag("id1", geometry); + const renderTag1: TestRenderTag = new TestRenderTag(tag1, undefined); + const tag2: TestTag = new TestTag("id2", geometry); + const renderTag2: TestRenderTag = new TestRenderTag(tag2, undefined); + + tagScene.add([renderTag1, renderTag2]); + + const result1: RenderTag = tagScene.get(renderTag1.tag.id); + + expect(result1).toBeDefined(); + expect(result1.tag.id).toBe(renderTag1.tag.id); + expect(result1).toBe(renderTag1); + + const result2: RenderTag = tagScene.get(renderTag2.tag.id); + + expect(result2).toBeDefined(); + expect(result2.tag.id).toBe(renderTag2.tag.id); + expect(result2).toBe(renderTag2); + }); +}); + +describe("TagScene.has", () => { + it("should have an added tag", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + + expect(tagScene.has(renderTag.tag.id)).toBe(true); + expect(tagScene.has("other-id")).toBe(false); + }); +}); + +describe("TagScene.remove", () => { + it("should remove a single tag", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + tagScene.remove([renderTag.tag.id]); + + expect(tagScene.has(renderTag.tag.id)).toBe(false); + expect(tagScene.get(renderTag.tag.id)).toBeUndefined(); + }); + + it("should remove a multiple tags", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag1: TestTag = new TestTag("id1", geometry); + const renderTag1: TestRenderTag = new TestRenderTag(tag1, undefined); + const tag2: TestTag = new TestTag("id2", geometry); + const renderTag2: TestRenderTag = new TestRenderTag(tag2, undefined); + + tagScene.add([renderTag1, renderTag2]); + tagScene.remove([renderTag1.tag.id, renderTag2.tag.id]); + + expect(tagScene.has(renderTag1.tag.id)).toBe(false); + expect(tagScene.get(renderTag1.tag.id)).toBeUndefined(); + + expect(tagScene.has(renderTag2.tag.id)).toBe(false); + expect(tagScene.get(renderTag2.tag.id)).toBeUndefined(); + }); +}); + +describe("TagScene.removeAll", () => { + it("should remove a single tag", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + tagScene.removeAll(); + + expect(tagScene.has(renderTag.tag.id)).toBe(false); + expect(tagScene.get(renderTag.tag.id)).toBeUndefined(); + }); + + it("should remove a multiple tags", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag1: TestTag = new TestTag("id1", geometry); + const renderTag1: TestRenderTag = new TestRenderTag(tag1, undefined); + const tag2: TestTag = new TestTag("id2", geometry); + const renderTag2: TestRenderTag = new TestRenderTag(tag2, undefined); + + tagScene.add([renderTag1, renderTag2]); + tagScene.removeAll(); + + expect(tagScene.has(renderTag1.tag.id)).toBe(false); + expect(tagScene.get(renderTag1.tag.id)).toBeUndefined(); + + expect(tagScene.has(renderTag2.tag.id)).toBe(false); + expect(tagScene.get(renderTag2.tag.id)).toBeUndefined(); + }); +}); + +class RendererMock implements THREE.Renderer { + public domElement: HTMLCanvasElement = document.createElement("canvas"); + + public render(s: THREE.Scene, c: THREE.Camera): void { return; } + public setSize(w: number, h: number, updateStyle?: boolean): void { return; } + public setClearColor(c: THREE.Color, o: number): void { return; } + public setPixelRatio(ratio: number): void { return; } + public clear(): void { return; } + public clearDepth(): void { return; } +} + +describe("TagScene.needsRender", () => { + it("should need render after changes", () => { + const renderer: THREE.Renderer = new RendererMock(); + spyOn(renderer, "render").and.stub(); + + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + expect(tagScene.needsRender).toBe(false); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + expect(tagScene.needsRender).toBe(true); + + tagScene.render(new THREE.PerspectiveCamera(), renderer); + expect(tagScene.needsRender).toBe(false); + + tagScene.remove([renderTag.tag.id]); + expect(tagScene.needsRender).toBe(true); + + tagScene.add([renderTag]); + tagScene.render(new THREE.PerspectiveCamera(), renderer); + tagScene.removeAll(); + expect(tagScene.needsRender).toBe(true); + + tagScene.render(new THREE.PerspectiveCamera(), renderer); + tagScene.update(); + + expect(tagScene.needsRender).toBe(true); + + tagScene.add([renderTag]); + tagScene.render(new THREE.PerspectiveCamera(), renderer); + tagScene.updateObjects(renderTag); + + expect(tagScene.needsRender).toBe(true); + }); +}); + +describe("TagScene.updateObjects", () => { + it("should update objects that are rendered", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + let first: boolean = true; + const firstObject: THREE.Object3D = new THREE.Object3D(); + const secondObject: THREE.Object3D = new THREE.Object3D(); + spyOn(renderTag, "getGLObjects").and.callFake( + () => { + if (first) { + first = false; + return [firstObject]; + } else { + return [secondObject]; + } + }); + + tagScene.add([renderTag]); + + expect(scene.children.length).toBe(1); + expect(scene.children[0]).toBe(firstObject); + expect(scene.children[0].uuid).toBe(firstObject.uuid); + + tagScene.updateObjects(renderTag); + + expect(scene.children.length).toBe(1); + expect(scene.children[0]).toBe(secondObject); + expect(scene.children[0].uuid).toBe(secondObject.uuid); + }); + + it("should throw if instance is not the same as added", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + + tagScene.add([renderTag]); + + const tagCopy: TestTag = new TestTag("id", geometry); + const renderTagCopy: TestRenderTag = new TestRenderTag(tagCopy, undefined); + + expect(() => { tagScene.updateObjects(renderTagCopy); }).toThrow(); + }); +}); + +describe("TagScene.intersectObjects", () => { + it("should intersect the retrievable object of the tag", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + spyOn(raycaster, "setFromCamera").and.stub(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + const retrievableObject: THREE.Object3D = new THREE.Object3D(); + spyOn(renderTag, "getRetrievableObjects").and.returnValue([retrievableObject]); + + tagScene.add([renderTag]); + + const intersectObjectsSpy: jasmine.Spy = spyOn(raycaster, "intersectObjects"); + intersectObjectsSpy.and.returnValue([]); + + const result: string[] = tagScene.intersectObjects([0, 0], new THREE.Camera()); + + expect(result.length).toBe(0); + + expect(intersectObjectsSpy.calls.count()).toBe(1); + expect(intersectObjectsSpy.calls.argsFor(0)[0].length).toBe(1); + expect(intersectObjectsSpy.calls.argsFor(0)[0][0]).toBe(retrievableObject); + expect(intersectObjectsSpy.calls.argsFor(0)[0][0].uuid).toBe(retrievableObject.uuid); + }); + + it("should return the tag id of the retrievable object", () => { + const scene: THREE.Scene = new THREE.Scene(); + const raycaster: THREE.Raycaster = new THREE.Raycaster(); + spyOn(raycaster, "setFromCamera").and.stub(); + const tagScene: TagScene = new TagScene(scene, raycaster); + + const geometry: RectGeometry = new RectGeometry([0, 0, 1, 1]); + const tag: TestTag = new TestTag("id", geometry); + const renderTag: TestRenderTag = new TestRenderTag(tag, undefined); + const retrievableObject: THREE.Object3D = new THREE.Object3D(); + spyOn(renderTag, "getRetrievableObjects").and.returnValue([retrievableObject]); + + tagScene.add([renderTag]); + + spyOn(raycaster, "intersectObjects").and.returnValue([{ object: retrievableObject }]); + + const result: string[] = tagScene.intersectObjects([0, 0], new THREE.Camera()); + + expect(result.length).toBe(1); + expect(result[0]).toBe(tag.id); + }); +}); diff --git a/src/component/tag/TagScene.ts b/src/component/tag/TagScene.ts index 0c5f9fac..c3881e10 100644 --- a/src/component/tag/TagScene.ts +++ b/src/component/tag/TagScene.ts @@ -128,7 +128,7 @@ export class TagScene { public render( perspectiveCamera: THREE.PerspectiveCamera, - renderer: THREE.WebGLRenderer): void { + renderer: THREE.Renderer): void { renderer.render(this._scene, perspectiveCamera);