diff --git a/src/globemap.ts b/src/globemap.ts index eb90542..b878f4c 100644 --- a/src/globemap.ts +++ b/src/globemap.ts @@ -159,7 +159,7 @@ export class GlobeMap implements IVisual { private root: HTMLElement; private rendererContainer: HTMLElement; private rendererCanvas: HTMLElement; - private camera: THREE.PerspectiveCamera; + public camera: THREE.PerspectiveCamera; private renderer: THREE.WebGLRenderer; private scene: THREE.Scene; private orbitControls: OrbitControls; @@ -176,12 +176,12 @@ export class GlobeMap implements IVisual { private locationsLoaded: number = 0; private initialLocationsLength: number = 0; private renderLoopEnabled = true; - private needsRender = false; + public needsRender = false; private mousePosNormalized: THREE.Vector2; private mousePos: THREE.Vector2; private rayCaster: THREE.Raycaster; private selectedBar: THREE.Object3D; - private hoveredBar: THREE.Object3D; + public hoveredBar: THREE.Object3D; private averageBarVector: THREE.Vector3; private controlContainer: HTMLElement; public colors: IColorPalette; @@ -197,9 +197,9 @@ export class GlobeMap implements IVisual { private formatMode: boolean = false; private pressKey: boolean = false; private barFromMouseDown: THREE.Object3D; - private subSelectedBar: IGlobeMapObject3DWithToolTipData; + public subSelectedBar: IGlobeMapObject3DWithToolTipData; private subSelectionService: powerbi.extensibility.IVisualSubSelectionService; - private subSelectionRegionOutlines: Map; + public subSelectionRegionOutlines: Map; private isFirstLoad: boolean = true; @@ -1219,7 +1219,7 @@ export class GlobeMap implements IVisual { return regionOutline; } - private worldToScreenPositions(bar: THREE.Object3D) { + public worldToScreenPositions(bar: THREE.Object3D) { const vector = new THREE.Vector3(); const canvas = this.renderer.domElement; diff --git a/test/visualBuilder.ts b/test/visualBuilder.ts index eaa2019..e10a5bd 100644 --- a/test/visualBuilder.ts +++ b/test/visualBuilder.ts @@ -41,11 +41,13 @@ export class GlobeMapBuilder extends VisualBuilderBase { super(width, height, "GlobeMap1447669447625"); } - public update(dataView: DataView[] | DataView, updateType?: VisualUpdateType): void { + public update(dataView: DataView[] | DataView, updateType?: VisualUpdateType, formatMode?: boolean, subSelections?: powerbi.visuals.CustomVisualSubSelection[]): void { let options: VisualUpdateOptions = { dataViews: Array.isArray(dataView) ? dataView : [dataView], viewport: this.viewport, - type: updateType! + type: updateType!, + formatMode: formatMode, + subSelections: subSelections }; this.visual.update(options); @@ -55,8 +57,11 @@ export class GlobeMapBuilder extends VisualBuilderBase { dataViews: DataView[] | DataView, fn: () => any, updateType: VisualUpdateType = GlobeMapBuilder.ChangeAllType, - timeout?: number): number { - this.update(dataViews, updateType); + formatMode: boolean = false, + timeout?: number, + subSelections?: powerbi.visuals.CustomVisualSubSelection[] + ): number { + this.update(dataViews, updateType, formatMode, subSelections); return renderTimeout(fn, timeout); } @@ -67,4 +72,36 @@ export class GlobeMapBuilder extends VisualBuilderBase { public get instance(): VisualClass { return this.visual; } + + public get canvasElement(): HTMLElement | null { + return this.element.querySelector("canvas"); + } + + public get controlsElements(): NodeListOf | null { + return this.element.querySelectorAll(".control"); + } + + public get rightControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--move-right"); + } + + public get leftControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--move-left"); + } + + public get upControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--move-up"); + } + + public get downControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--move-down"); + } + + public get zoomUpControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--zoom-up"); + } + + public get zoomDownControlElement(): HTMLElement | null { + return this.element.querySelector(".control.js-control--zoom-down"); + } } diff --git a/test/visualData.ts b/test/visualData.ts index 59c8afd..e8755bb 100644 --- a/test/visualData.ts +++ b/test/visualData.ts @@ -30,6 +30,7 @@ import DataView = powerbi.DataView; import { ValueType } from "powerbi-visuals-utils-typeutils/lib/valueType"; import { TestDataViewBuilder } from "powerbi-visuals-utils-testutils/lib/dataViewBuilder/testDataViewBuilder"; import { getRandomNumbers } from "powerbi-visuals-utils-testutils"; +import { ILocationDictionary } from "../src/interfaces/locationInterfaces"; export class GlobeMapData extends TestDataViewBuilder { public static ColumnSource: string = "Location"; @@ -146,4 +147,28 @@ export class GlobeMapData extends TestDataViewBuilder { } ], columnNames).build(); } + + public getCoordinatesMock(): ILocationDictionary{ + return { + "addis ababa, ethiopia": {latitude: 9.03582859, longitude: 38.75241089}, + "ahmedabad, india": {latitude: 23.0145092, longitude: 72.59175873}, + "cairo, egypt": {latitude: 30.04348755, longitude: 31.23529243}, + "cape town, south africa": {latitude: -33.92710876, longitude: 18.42006111}, + "casablanca, morocco": {latitude: 33.59451294, longitude: -7.6200285}, + "chennai, india": {latitude: 13.07209206, longitude: 80.20185852}, + "durban, south africa": {latitude: -29.88188934, longitude: 30.98084259}, + "jakarta, indonesia": {latitude: -6.17475653, longitude: 106.82707214}, + "jeddah, saudi arabia": {latitude: 21.48730469, longitude: 39.18133545}, + "lagos, nigeria": {latitude: 6.45505762, longitude: 3.39417958}, + "lima, peru": {latitude: -12.06210613, longitude: -77.03652191}, + "london, united kingdom": {latitude: 51.50740814, longitude: -0.12772401}, + "mexico city, mexico": {latitude: 19.43267822, longitude: -99.13420868}, + "new taipei city, republic of china": {latitude: 25.01170921, longitude: 121.46588135}, + "riyadh, saudi arabia": {latitude: 24.69496918, longitude: 46.72412872}, + "shanghai, china": {latitude: 31.23036957, longitude: 121.47370148}, + "shenzhen, china": {latitude: 22.54368019, longitude: 114.0579071}, + "surat, india": {latitude: 21.20350838, longitude: 72.83922577}, + "tehran, iran": {latitude: 35.68925095, longitude: 51.38959885} + }; + } } diff --git a/test/visualTest.ts b/test/visualTest.ts index 8587c08..7621e17 100644 --- a/test/visualTest.ts +++ b/test/visualTest.ts @@ -37,11 +37,15 @@ import { GlobeMapData as GlobeMapDataViewBuilder } from "./visualData"; import { GlobeMap as VisualClass } from "../src/globemap"; import { GlobeMapColumns } from "../src/columns"; -import { TileMap, ITileGapObject } from "../src/interfaces/dataInterfaces"; +import { TileMap, ITileGapObject, IGlobeMapObject3DWithToolTipData } from "../src/interfaces/dataInterfaces"; import capabilities from '../capabilities.json'; import PrimitiveValue = powerbi.PrimitiveValue; import { ILocationKeyDictionary } from "../src/interfaces/locationInterfaces"; +import { PointerType, createSelectionId, d3MouseDown, renderTimeout } from "powerbi-visuals-utils-testutils"; +import SubSelectionOutlineVisibility = powerbi.visuals.SubSelectionOutlineVisibility; +import ArcSubSelectionOutline = powerbi.visuals.ArcSubSelectionOutline; +import { DataPointReferences } from "../src/settings"; describe("GlobeMap", () => { let visualBuilder: GlobeMapBuilder, @@ -49,20 +53,10 @@ describe("GlobeMap", () => { defaultDataViewBuilder: GlobeMapDataViewBuilder, dataView: DataView; - beforeAll(() => { + beforeAll(async() => { visualBuilder = new GlobeMapBuilder(1024, 1024); visualInstance = visualBuilder.instance; - }); - - beforeEach(() => { defaultDataViewBuilder = new GlobeMapDataViewBuilder(); - dataView = defaultDataViewBuilder.getDataView(); - }); - - describe("DOM tests", () => { - - beforeAll(async () => { - defaultDataViewBuilder = new GlobeMapDataViewBuilder(); dataView = defaultDataViewBuilder.getDataView(); let categoricalColumns = GlobeMapColumns.getCategoricalColumns(dataView); @@ -83,8 +77,14 @@ describe("GlobeMap", () => { if (Object.keys(coordinates).length > 0) { await visualInstance.cacheManager.saveCoordinates(coordinates); } - }); + }); + + beforeEach(() => { + defaultDataViewBuilder = new GlobeMapDataViewBuilder(); + dataView = defaultDataViewBuilder.getDataView(); + }); + describe("DOM tests", () => { it("canvas element created", () => { console.log("dom test started"); @@ -216,40 +216,527 @@ describe("GlobeMap", () => { ]; const expectedResult = [ - { - "000": "https://ecn.t1.tiles.virtualearth.net/tiles/r000.jpeg?mkt=en-US&shading=hill", - "001": "https://ecn.t1.tiles.virtualearth.net/tiles/r001.jpeg?mkt=en-US&shading=hill", - "002": "https://ecn.t1.tiles.virtualearth.net/tiles/r002.jpeg?mkt=en-US&shading=hill", - "003": "https://ecn.t1.tiles.virtualearth.net/tiles/r003.jpeg?mkt=en-US&shading=hill" - } - ]; + { + "000": "https://t0.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/000?mkt=en-US&it=G,L&on=z", + "001": "https://t1.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/001?mkt=en-US&it=G,L&on=z", + "002": "https://t2.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/002?mkt=en-US&it=G,L&on=z", + "003": "https://t3.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/003?mkt=en-US&it=G,L&on=z" + }]; const culture: string = "en-US"; - it("for not valid input", () => { + it("for not valid input", async () => { const expectedResult = []; const tiles = [[]]; - tiles.forEach((tile) => { - visualInstance.extendTiles(JSON.stringify(tile), culture) - .then((data: TileMap[]) => { - expect(data.length).toBe(expectedResult.length); - }); + const data: TileMap[] = await visualInstance.extendTiles(JSON.stringify(tiles[0]), culture); + expect(data.length).toBe(expectedResult.length); + }); + + it("for valid input", async () => { + const data: TileMap[] = await visualInstance.extendTiles(JSON.stringify(tiles), culture); + expect(data).not.toBeNull(); + for (let i = 0; i < data.length; i++) { + const tile: TileMap = data[i]; + for (let key in tile) { + tile[key] = tile[key].replace(/g=\w+&/g, ''); + expect(tile[key]).toBe(expectedResult[i][key]); + } + } + }); + }); + }); + + describe("OnObject tests", () => { + beforeAll((done) => { + const coordinates = defaultDataViewBuilder.getCoordinatesMock(); + if (Object.keys(coordinates).length > 0) { + visualInstance.cacheManager.saveCoordinates(coordinates) + .then(() => { + //update with formatMode=true + visualBuilder.updateRenderTimeout(dataView, done, powerbi.VisualUpdateType.Data, true, 500); }); + } + }); + + describe("hover bar tests", () => { + it("should create hover outline when hovering bar", (done) => { + const barToHover = visualInstance.barsGroup.children[0]; + visualInstance.hoveredBar = barToHover; + + const pointerMove = new PointerEvent("pointermove"); + visualBuilder.canvasElement?.dispatchEvent(pointerMove); + + expect(visualInstance.hoveredBar).toBeDefined(); + const hoveredBarPosition = visualInstance.worldToScreenPositions(visualInstance.hoveredBar); + + const hoveredBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(hoveredBarOutline).toBeDefined(); + + const outlineCenter = (hoveredBarOutline as ArcSubSelectionOutline).center; + expect(hoveredBarPosition).toEqual(outlineCenter); + done(); }); + it("should delete hover outline when not hovering bar", (done) => { + visualInstance.hoveredBar = null; + + const pointerMove = new PointerEvent("pointermove"); + visualBuilder.canvasElement?.dispatchEvent(pointerMove); - it("for valid input", () => { - visualInstance.extendTiles(JSON.stringify(tiles), culture) - .then((data: TileMap[]) => { - expect(data).not.toBeNull(); - for (let i = 0; data.length; i++) { - const tile: TileMap = data[i]; - for (let key in tile) { - tile[key] = tile[key].replace(/g=\w+&/g, ''); - } - expect(data[i]).toBe(expectedResult[i]); - } - }); + + const hoveredBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(hoveredBarOutline).toBeUndefined(); + expect(visualInstance.hoveredBar).toBeFalsy(); + done(); + }); + it("should create hover outline when hovering bar and keep active outline", (done) => { + //subselect bar + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.subSelectedBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + visualInstance.needsRender = true; + + renderTimeout(() => { + const activeOutlineBeforeHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const hoverOutlineBeforeHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(activeOutlineBeforeHover).toBeDefined(); + expect(hoverOutlineBeforeHover).toBeUndefined(); + const activeOutlineBeforeHoverCenter = (activeOutlineBeforeHover as ArcSubSelectionOutline).center; + + const barToHover = visualInstance.barsGroup.children[1] as IGlobeMapObject3DWithToolTipData; + const barToHoverIdentity = createSelectionId("hover"); + barToHover.identity = barToHoverIdentity; + visualInstance.hoveredBar = barToHover; + + const pointerMove = new PointerEvent("pointermove"); + visualBuilder.canvasElement?.dispatchEvent(pointerMove); + + expect(visualInstance.hoveredBar).toBeDefined(); + const activeOutlineAfterHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const hoverOutlineAfterHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(hoverOutlineAfterHover).toBeDefined(); + expect(activeOutlineAfterHover).toBeDefined(); + + const activeOutlineAfterHoverCenter = (activeOutlineAfterHover as ArcSubSelectionOutline).center; + const hoverOutlineAfterHoverCenter = (hoverOutlineAfterHover as ArcSubSelectionOutline).center; + const hoveredBarPosition = visualInstance.worldToScreenPositions(visualInstance.hoveredBar); + expect(activeOutlineBeforeHoverCenter).toEqual(activeOutlineAfterHoverCenter); + expect(hoveredBarPosition).toEqual(hoverOutlineAfterHoverCenter); + done(); + }, 200); + }); + it("should not create hover outline when hovering already active bar", (done) => { + //subselect bar + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.subSelectedBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + visualInstance.needsRender = true; + + renderTimeout(() => { + const activeOutlineBeforeHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const hoverOutlineBeforeHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(activeOutlineBeforeHover).toBeDefined(); + expect(hoverOutlineBeforeHover).toBeDefined(); + const activeOutlineBeforeHoverCenter = (activeOutlineBeforeHover as ArcSubSelectionOutline).center; + + const barToHover = visualInstance.barsGroup.children[0]; + visualInstance.hoveredBar = barToHover; + + const pointerMove = new PointerEvent("pointermove"); + visualBuilder.canvasElement?.dispatchEvent(pointerMove); + + expect(visualInstance.hoveredBar).toBeDefined(); + const activeOutlineAfterHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const hoverOutlineAfterHover = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Hover)?.outline; + expect(hoverOutlineAfterHover).toBeUndefined(); + expect(activeOutlineAfterHover).toBeDefined(); + + const activeOutlineAfterHoverCenter = (activeOutlineAfterHover as ArcSubSelectionOutline).center; + expect(activeOutlineBeforeHoverCenter).toEqual(activeOutlineAfterHoverCenter); + done(); + }, 200); }); }); + + describe("bar selection tests", () => { + describe("fast click", () => { + it("should create active outline for selected bar", (done) => { + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.hoveredBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + done(); + }, 200); + }); + + it("should create new active outline for selected bar and delete previous active outline", (done) => { + const previousActiveOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(previousActiveOutline).toBeDefined(); + const previousActiveOutlineCenter = (previousActiveOutline as ArcSubSelectionOutline).center; + + const barToSubselect = visualInstance.barsGroup.children[1]; + visualInstance.hoveredBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + expect(outlineCenter).not.toEqual(previousActiveOutlineCenter); + done(); + }, 200); + }); + + it("should delete active outlines when clicking not on bar", (done) => { + const previousActiveOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(previousActiveOutline).toBeDefined(); + + visualInstance.hoveredBar = null; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeFalsy(); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeUndefined(); + + done(); + }, 200); + }); + }); + + describe("slow click", () => { + it("should create active outline for selected bar", (done) => { + expect(visualInstance.subSelectedBar).toBeFalsy(); + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.hoveredBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + renderTimeout(() => { + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + done(); + }, 200); + }, 250) + + }); + + it("should create new active outline for selected bar and delete previous active outline", (done) => { + const previousActiveOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(previousActiveOutline).toBeDefined(); + const previousActiveOutlineCenter = (previousActiveOutline as ArcSubSelectionOutline).center; + + const barToSubselect = visualInstance.barsGroup.children[1]; + visualInstance.hoveredBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + renderTimeout(() => { + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + expect(outlineCenter).not.toEqual(previousActiveOutlineCenter); + done(); + }, 200); + }, 250); + }); + + it("active outline should remain when clicking not on bar", (done) => { + const activeOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(activeOutline).toBeDefined(); + const activeOutlineCenter = (activeOutline as ArcSubSelectionOutline).center; + + visualInstance.hoveredBar = null; + + const pointerDown = new PointerEvent("pointerdown"); + const pointerUp = new PointerEvent("pointerup"); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + renderTimeout(() => { + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + expect(activeOutlineCenter).toEqual(outlineCenter); + done(); + }, 200); + }, 250); + }); + + it("should recalculate active outline for selected bar after pointerdown and pointemove events", (done) => { + const activeOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(activeOutline).toBeDefined(); + const activeOutlineCenter = (activeOutline as ArcSubSelectionOutline).center; + + visualInstance.hoveredBar = null; + + const pointerDown = new PointerEvent("pointerdown", {pointerType: PointerType.mouse, button: 0}); + const pointerMove = new PointerEvent("pointermove", {clientX: 50, clientY: 50, buttons: 1, pointerType: PointerType.mouse}); + const pointerUp = new PointerEvent("pointerup", {pointerType: PointerType.mouse, button: 0}); + visualBuilder.canvasElement?.dispatchEvent(pointerDown); + visualBuilder.canvasElement?.ownerDocument.dispatchEvent(pointerMove); + renderTimeout(() => { + visualBuilder.canvasElement?.dispatchEvent(pointerUp); + renderTimeout(() => { + expect(visualInstance.subSelectedBar).toBeDefined(); + const subselectedBarPosition = visualInstance.worldToScreenPositions(visualInstance.subSelectedBar); + + const subselectedBarOutline = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(subselectedBarOutline).toBeDefined(); + + const outlineCenter = (subselectedBarOutline as ArcSubSelectionOutline).center; + expect(subselectedBarPosition).toEqual(outlineCenter); + expect(activeOutlineCenter).not.toEqual(outlineCenter); + done(); + }, 500); + }, 500); + }); + }); + }); + + describe("wheel event tests:", () => { + beforeAll((done) => { + //subselect bar before testing wheel behavior + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.subSelectedBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + visualInstance.needsRender = true; + + renderTimeout(done, 500); + }); + + it("should recalculate Active outline after wheel zoom in", (done) => { + const outlineBeforeZoom = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeZoom = (outlineBeforeZoom as ArcSubSelectionOutline).center; + const wheelEvent = new WheelEvent("wheel", {deltaY: -125}); + visualBuilder.canvasElement?.dispatchEvent(wheelEvent); + renderTimeout(() => { + const barAfterZoom = visualInstance.subSelectedBar; + const barPositionAfterZoom = visualInstance.worldToScreenPositions(barAfterZoom); + + const outlineAfterZoom = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterZoom).toBeDefined(); + + const outlineCenterAfterZoom = (outlineAfterZoom as ArcSubSelectionOutline).center; + expect(barPositionAfterZoom).toEqual(outlineCenterAfterZoom); + expect(outlineCenterBeforeZoom.x).toBeGreaterThan(outlineCenterAfterZoom.x); + expect(outlineCenterBeforeZoom.y).toBeLessThan(outlineCenterAfterZoom.y); + done(); + }, 500); + }); + + it("should recalculate Active outline after wheel zoom out", (done) => { + const outlineBeforeZoom = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeZoom = (outlineBeforeZoom as ArcSubSelectionOutline).center; + const wheelEvent = new WheelEvent("wheel", {deltaY: 125}); + visualBuilder.canvasElement?.dispatchEvent(wheelEvent); + renderTimeout(() => { + const barAfterZoom = visualInstance.subSelectedBar; + const barPositionAfterZoom = visualInstance.worldToScreenPositions(barAfterZoom); + + const outlineAfterZoom = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterZoom).toBeDefined(); + + const outlineCenterAfterZoom = (outlineAfterZoom as ArcSubSelectionOutline).center; + expect(barPositionAfterZoom).toEqual(outlineCenterAfterZoom); + expect(outlineCenterBeforeZoom.x).toBeLessThan(outlineCenterAfterZoom.x); + expect(outlineCenterBeforeZoom.y).toBeGreaterThan(outlineCenterAfterZoom.y); + done(); + }, 500); + }); + }); + + describe("control buttons tests:", () => { + beforeAll((done) => { + //subselect bar before testing control buttons + const barToSubselect = visualInstance.barsGroup.children[0]; + visualInstance.subSelectedBar = barToSubselect as IGlobeMapObject3DWithToolTipData; + visualInstance.needsRender = true; + + renderTimeout(done, 500); + }); + + it("should recalculate Active outline after right control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + d3MouseDown(visualBuilder.rightControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.x).toBeGreaterThan(outlineCenterAfterClick.x); + done(); + }, 500); + }); + + it("should recalculate Active outline after left control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + d3MouseDown(visualBuilder.leftControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.x).toBeLessThan(outlineCenterAfterClick.x); + done(); + }, 500); + }); + + it("should recalculate Active outline after up control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + d3MouseDown(visualBuilder.upControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.y).toBeLessThan(outlineCenterAfterClick.y); + done(); + }, 500); + }); + + it("should recalculate Active outline after down control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + d3MouseDown(visualBuilder.downControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.y).toBeGreaterThan(outlineCenterAfterClick.y); + done(); + }, 500); + }); + + it("should recalculate Active outline after zoom up control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + d3MouseDown(visualBuilder.zoomUpControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.x).toBeGreaterThan(outlineCenterAfterClick.x); + expect(outlineCenterBeforeClick.y).toBeLessThan(outlineCenterAfterClick.y); + done(); + }, 500); + }); + + it("should recalculate Active outline after zoom down control button click", (done) => { + const outlineBeforeClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + const outlineCenterBeforeClick = (outlineBeforeClick as ArcSubSelectionOutline).center; + + d3MouseDown(visualBuilder.zoomDownControlElement, 0, 0); + renderTimeout(() => { + const barAfterClick = visualInstance.subSelectedBar; + const barPositionAfterClick = visualInstance.worldToScreenPositions(barAfterClick); + + const outlineAfterClick = visualInstance.subSelectionRegionOutlines.get(SubSelectionOutlineVisibility.Active)?.outline; + expect(outlineAfterClick).toBeDefined(); + + const outlineCenterAfterClick = (outlineAfterClick as ArcSubSelectionOutline).center; + expect(barPositionAfterClick).toEqual(outlineCenterAfterClick); + expect(outlineCenterBeforeClick.x).toBeLessThan(outlineCenterAfterClick.x); + expect(outlineCenterBeforeClick.y).toBeGreaterThan(outlineCenterAfterClick.y); + done(); + }, 500); + }); + }); + + it("context menu animation", (done) => { + const barIdentity = createSelectionId("subselection"); + const barToSubselect = (visualInstance.barsGroup.children[0] as IGlobeMapObject3DWithToolTipData); + barToSubselect.identity = barIdentity; + const cameraPositionBeforeAnimation = visualInstance.camera.position.clone(); + + const subselectionsFromContextMenu: powerbi.visuals.CustomVisualSubSelection[] = [{ + customVisualObjects: [{ + objectName: DataPointReferences.fill.objectName, + selectionId: barIdentity + }], + displayName: "subselectionFromContextMenu", + subSelectionType: powerbi.visuals.SubSelectionStylesType.Shape, + selectionOrigin: {x:0, y:0}, + showUI: true + }]; + visualInstance.hoveredBar = null; + visualInstance.subSelectedBar = null; + + visualBuilder.updateRenderTimeout(dataView, () => { + const cameraPositionAfterAnimation = visualInstance.camera.position; + expect(cameraPositionBeforeAnimation).not.toEqual(cameraPositionAfterAnimation); + done(); + }, powerbi.VisualUpdateType.Data, true, 1500, subselectionsFromContextMenu); + }); }); });