import {expect} from "chai" import * as sinon from "sinon" import {Keys} from "@bokehjs/core/dom" import {create_hit_test_result_from_hits} from "@bokehjs/core/hittest" import {Rect, RectView} from "@bokehjs/models/glyphs/rect" import {Plot} from "@bokehjs/models/plots/plot" import {Range1d} from "@bokehjs/models/ranges/range1d" import {GlyphRenderer} from "@bokehjs/models/renderers/glyph_renderer" import {ColumnDataSource} from "@bokehjs/models/sources/column_data_source" import {BoxEditTool, BoxEditToolView} from "@bokehjs/models/tools/edit/box_edit_tool" import {make_gesture_event, make_tap_event, make_move_event, make_key_event} from "./utils" export interface BoxEditTestCase { data: {[key: string]: (number | null)[]} data_source: ColumnDataSource draw_tool_view: BoxEditToolView glyph_view: RectView } const make_testcase = function(): BoxEditTestCase { // Note default plot dimensions is 600 x 600 (height x width) const plot = new Plot({ x_range: new Range1d({start: -1, end: 1}), y_range: new Range1d({start: -1, end: 1}), }) const plot_view: any = new plot.default_view({model: plot, parent: null}).build() const data = { x: [0, 0.5, 1], y: [0, 0.5, 1], width: [0.1, 0.2, 0.3], height: [0.3, 0.2, 0.1], z: [null, null, null], } const data_source = new ColumnDataSource({data}) const glyph = new Rect({ x: {field: "x"}, y: {field: "y"}, width: {field: "width"}, height: {field: "height"}, }) const glyph_renderer: any = new GlyphRenderer({glyph, data_source}) // Untyped to access GlyphView const glyph_renderer_view: any = new glyph_renderer.default_view({ model: glyph_renderer, parent: plot_view, }) const draw_tool = new BoxEditTool({ active: true, renderers: [glyph_renderer], empty_value: "Test", }) plot.add_tools(draw_tool) const draw_tool_view = plot_view.tool_views[draw_tool.id] plot_view.renderer_views[glyph_renderer.id] = glyph_renderer_view return { data, data_source, draw_tool_view, glyph_view: glyph_renderer_view.glyph, } } describe("BoxEditTool", function(): void { describe("Model", function(): void { it("should create proper tooltip", function(): void { const tool = new BoxEditTool() expect(tool.tooltip).to.be.equal('Box Edit Tool') const custom_tool = new BoxEditTool({custom_tooltip: 'Box Edit Custom'}) expect(custom_tool.tooltip).to.be.equal('Box Edit Custom') }) }) describe("View", function(): void { it("should select rect on tap", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(create_hit_test_result_from_hits([[1, 0]])) const tap_event = make_tap_event(300, 300) testcase.draw_tool_view._tap(tap_event) expect(testcase.data_source.selected.indices).to.be.deep.equal([1]) }) it("should select multiple rect on shift-tap", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(create_hit_test_result_from_hits([[1, 0]])) let tap_event = make_tap_event(300, 300) testcase.draw_tool_view._tap(tap_event) hit_test_stub.returns(create_hit_test_result_from_hits([[2, 0]])) tap_event = make_tap_event(560, 560, true) testcase.draw_tool_view._tap(tap_event) expect(testcase.data_source.selected.indices).to.be.deep.equal([2, 1]) }) it("should delete selected on delete key", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(create_hit_test_result_from_hits([[1, 0]])) const tap_event = make_tap_event(300, 300) testcase.draw_tool_view._tap(tap_event) const moveenter_event = make_move_event(300, 300) const keyup_event = make_key_event(Keys.Backspace) testcase.draw_tool_view._move_enter(moveenter_event) testcase.draw_tool_view._keyup(keyup_event) expect(testcase.data_source.selected.indices).to.be.deep.equal([]) expect(testcase.data_source.data.x).to.be.deep.equal([0, 1]) expect(testcase.data_source.data.y).to.be.deep.equal([0, 1]) expect(testcase.data_source.data.z).to.be.deep.equal([null, null]) }) it("should clear selection on escape key", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(create_hit_test_result_from_hits([[1, 0]])) const tap_event = make_tap_event(300, 300) testcase.draw_tool_view._tap(tap_event) const moveenter_event = make_move_event(300, 300) const keyup_event = make_key_event(Keys.Esc) testcase.draw_tool_view._move_enter(moveenter_event) testcase.draw_tool_view._keyup(keyup_event) expect(testcase.data_source.selected.indices).to.be.deep.equal([]) expect(testcase.data_source.data).to.be.deep.equal(testcase.data) }) it("should drag selected on pan", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(create_hit_test_result_from_hits([[1, 0]])) const tap_event = make_tap_event(300, 300) testcase.draw_tool_view._tap(tap_event) let drag_event = make_gesture_event(300, 300) testcase.draw_tool_view._pan_start(drag_event) expect(testcase.draw_tool_view._basepoint).to.be.deep.equal([300, 300]) drag_event = make_gesture_event(200, 200) testcase.draw_tool_view._pan(drag_event) expect(testcase.draw_tool_view._basepoint).to.be.deep.equal([200, 200]) drag_event = make_gesture_event(200, 200) testcase.draw_tool_view._pan_end(drag_event) expect(testcase.draw_tool_view._basepoint).to.be.equal(null) expect(testcase.data_source.data.x).to.be.deep.equal([0, 0.14601769911504425, 1]) expect(testcase.data_source.data.y).to.be.deep.equal([0, 0.8389830508474576, 1]) expect(testcase.data_source.data.width).to.be.deep.equal([0.1, 0.2, 0.3]) expect(testcase.data_source.data.height).to.be.deep.equal([0.3, 0.2, 0.1]) expect(testcase.data_source.data.z).to.be.deep.equal([null, null, null]) }) it("should draw box on pan", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(null) let drag_event = make_gesture_event(300, 300, true) testcase.draw_tool_view._pan_start(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) drag_event = make_gesture_event(200, 200, true) testcase.draw_tool_view._pan(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) testcase.draw_tool_view._pan_end(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.equal(null) expect(testcase.data_source.selected.indices).to.be.deep.equal([]) expect(testcase.data_source.data.x).to.be.deep.equal([0, 0.5, 1, -0.1327433628318584]) expect(testcase.data_source.data.y).to.be.deep.equal([0, 0.5, 1, 0.1694915254237288]) expect(testcase.data_source.data.width).to.be.deep.equal([0.1, 0.2, 0.3, 0.35398230088495575]) expect(testcase.data_source.data.height).to.be.deep.equal([0.3, 0.2, 0.1, 0.3389830508474576]) expect(testcase.data_source.data.z).to.be.deep.equal([null, null, null, "Test"]) }) it("should draw and pop box on pan", function(): void { const testcase = make_testcase() testcase.draw_tool_view.model.num_objects = 3 const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(null) let drag_event = make_gesture_event(300, 300, true) testcase.draw_tool_view._pan_start(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) drag_event = make_gesture_event(200, 200, true) testcase.draw_tool_view._pan(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) testcase.draw_tool_view._pan_end(drag_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.equal(null) expect(testcase.data_source.selected.indices).to.be.deep.equal([]) expect(testcase.data_source.data.x).to.be.deep.equal([0.5, 1, -0.1327433628318584]) expect(testcase.data_source.data.y).to.be.deep.equal([0.5, 1, 0.1694915254237288]) expect(testcase.data_source.data.width).to.be.deep.equal([0.2, 0.3, 0.35398230088495575]) expect(testcase.data_source.data.height).to.be.deep.equal([0.2, 0.1, 0.3389830508474576]) expect(testcase.data_source.data.z).to.be.deep.equal([null, null, "Test"]) }) it("should draw box on doubletap and move", function(): void { const testcase = make_testcase() const hit_test_stub = sinon.stub(testcase.glyph_view, "hit_test") hit_test_stub.returns(null) const tap_event1 = make_tap_event(300, 300, true) testcase.draw_tool_view._doubletap(tap_event1) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) const move_event = make_move_event(200, 200) testcase.draw_tool_view._move(move_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.deep.equal([300, 300]) const tap_event2 = make_tap_event(200, 200, true) testcase.draw_tool_view._doubletap(tap_event2) expect(testcase.draw_tool_view._draw_basepoint).to.be.equal(null) expect(testcase.data_source.selected.indices).to.be.deep.equal([]) expect(testcase.data_source.data.x).to.be.deep.equal([0, 0.5, 1, -0.1327433628318584]) expect(testcase.data_source.data.y).to.be.deep.equal([0, 0.5, 1, 0.1694915254237288]) expect(testcase.data_source.data.width).to.be.deep.equal([0.1, 0.2, 0.3, 0.35398230088495575]) expect(testcase.data_source.data.height).to.be.deep.equal([0.3, 0.2, 0.1, 0.3389830508474576]) expect(testcase.data_source.data.z).to.be.deep.equal([null, null, null, "Test"]) }) it("should not draw box on doubletap when tool inactive", function(): void { const testcase = make_testcase() testcase.draw_tool_view.model.active = false const tap_event = make_tap_event(300, 300, true) testcase.draw_tool_view._doubletap(tap_event) expect(testcase.draw_tool_view._draw_basepoint).to.be.equal(undefined) }) }) })