UNPKG

leaflet.glify

Version:
664 lines (654 loc) 21.7 kB
import { LatLng, LatLngBounds, Map, Point } from "leaflet"; import { FeatureCollection, Point as GeoPoint } from "geojson"; import { IPointVertex, IPointsSettings, Points } from "./points"; import { ICanvasOverlayDrawEvent } from "./canvas-overlay"; jest.mock("./canvas-overlay"); function getPoints(settings?: Partial<IPointsSettings>): Points { const element = document.createElement("div"); const map = new Map(element); const data = [[1, 1]]; return new Points({ size: 5, map, data, vertexShaderSource: " ", fragmentShaderSource: " ", latitudeKey: 0, longitudeKey: 1, ...settings, }); } describe("Points", () => { describe("size", () => { describe("when this.settings.size is falsey", () => { it("returns null", () => { const points = getPoints(); delete points.settings.size; expect(points.size).toBeNull(); }); }); }); describe("constructor", () => { let setupSpy: jest.SpyInstance; let renderSpy: jest.SpyInstance; beforeEach(() => { setupSpy = jest.spyOn(Points.prototype, "setup"); renderSpy = jest.spyOn(Points.prototype, "render"); }); afterEach(() => { setupSpy.mockRestore(); renderSpy.mockRestore(); }); it("sets this.settings", () => { const points = getPoints(); expect(points.settings.color).toBe(Points.defaults.color); }); it("sets this.active = true", () => { const points = getPoints(); expect(points.active).toBe(true); }); describe("when settings.data is an array", () => { it('sets this.dataFormat to "Array"', () => { expect(getPoints({ data: [[1, 1]] }).dataFormat).toEqual("Array"); }); }); describe("when settings.data is a FeatureCollection", () => { it('sets this.dataFormat to "Array"', () => { const data: FeatureCollection<GeoPoint> = { type: "FeatureCollection", features: [], }; expect( getPoints({ data, }).dataFormat ).toEqual("GeoJson.FeatureCollection"); }); }); describe("when settings.data is unknown", () => { it("throws", () => { expect(() => { getPoints({ data: undefined, }); }).toThrow(); }); }); describe("when the map projector is not SphericalMercator", () => { let warnSpy: jest.SpyInstance; beforeEach(() => { warnSpy = jest.spyOn(console, "warn"); }); afterEach(() => { warnSpy.mockRestore(); }); it("warns", () => { const element = document.createElement("div"); const map = new Map(element); expect(map.options.crs).not.toBeFalsy(); (map.options.crs ?? { code: "" }).code = "123"; getPoints({ map }); expect(warnSpy).toHaveBeenCalledWith( "layer designed for SphericalMercator, alternate detected" ); }); }); it("calls this.setup", () => { getPoints(); expect(setupSpy).toHaveBeenCalled(); }); it("calls this.render", () => { getPoints(); expect(renderSpy).toHaveBeenCalled(); }); }); describe("render", () => { let resetVerticesSpy: jest.SpyInstance; beforeEach(() => { resetVerticesSpy = jest.spyOn(Points.prototype, "resetVertices"); }); afterEach(() => { resetVerticesSpy.mockRestore(); }); it("calls this.resetVertices", () => { const points = getPoints(); resetVerticesSpy.mockReset(); points.render(); expect(resetVerticesSpy).toHaveBeenCalled(); }); it('sets this.matrix from this.getUniformLocation("matrix")', () => { const points = getPoints(); points.matrix = null; points.render(); expect(points.matrix).toBe(points.uniformLocations.matrix); }); it("sets this.typedVertices correctly", () => { const points = getPoints(); points.typedVertices = new Float32Array(0); points.render(); expect(points.typedVertices).toEqual(new Float32Array(points.vertices)); }); it("sets this.mapMatrix size to canvas", () => { const points = getPoints(); const setSizeSpy = jest.spyOn(points.mapMatrix, "setSize"); points.render(); expect(setSizeSpy).toHaveBeenCalledWith( points.canvas.width, points.canvas.height ); }); it("calls this.gl.viewport correctly", () => { const points = getPoints(); const viewportSpy = jest.spyOn(points.gl, "viewport"); points.render(); expect(viewportSpy).toHaveBeenCalledWith( 0, 0, points.canvas.width, points.canvas.height ); }); it("calls this.gl.uniformMatrix4fv correctly", () => { const points = getPoints(); const uniformMatrix4fvSpy = jest.spyOn(points.gl, "uniformMatrix4fv"); points.render(); expect(uniformMatrix4fvSpy).toHaveBeenCalledWith( points.matrix, false, points.mapMatrix.array ); }); it("calls this.gl.bindBuffer correctly", () => { const points = getPoints(); const bindBufferSpy = jest.spyOn(points.gl, "bindBuffer"); points.render(); expect(bindBufferSpy).toHaveBeenCalledWith( points.gl.ARRAY_BUFFER, points.buffers.vertices ); }); it("calls this.gl.bufferData correctly", () => { const points = getPoints(); const bufferDataSpy = jest.spyOn(points.gl, "bufferData"); points.render(); expect(bufferDataSpy).toHaveBeenCalledWith( points.gl.ARRAY_BUFFER, points.typedVertices, points.gl.STATIC_DRAW ); }); it("calls this.attachShaderVariables correctly", () => { const points = getPoints(); const attachShaderVariablesSpy = jest.spyOn( points, "attachShaderVariables" ); points.render(); expect(attachShaderVariablesSpy).toHaveBeenCalledWith(4); }); it("calls layer.redraw", () => { const points = getPoints(); const redrawSpy = jest.spyOn(points.layer, "redraw"); points.render(); expect(redrawSpy).toHaveBeenCalled(); }); }); describe("getPointLookup", () => { describe("when key is not defined on this.latLngLookup", () => { it("sets this.latLngLookup as key and returns defined array", () => { const points = getPoints(); delete points.latLngLookup.key; const result = points.getPointLookup("key"); expect(result).toEqual([]); expect(points.latLngLookup.key).toBe(result); }); }); describe("when key is defined on this.latLngLookup", () => { it("returns defined array", () => { const points = getPoints(); const value = (points.latLngLookup.key = []); const result = points.getPointLookup("key"); expect(result).toBe(value); }); }); }); describe("addLookup", () => { it("pushes lookup to this.latLngLookup and this.allLatLngLookup", () => { const points = getPoints(); const lookup: IPointVertex = { latLng: new LatLng(1, 2), pixel: { x: 0, y: 1 }, chosenColor: { r: 1, g: 1, b: 1, a: 1, }, chosenSize: 1, key: "key", }; expect(points.latLngLookup.key).toBeUndefined(); points.addLookup(lookup); expect(points.latLngLookup.key).toContain(lookup); expect(points.allLatLngLookup).toContain(lookup); }); }); describe("resetVertices", () => { it("empties this.latLngLookup", () => { const points = getPoints(); const oldValue = points.latLngLookup; points.resetVertices(); expect(points.latLngLookup).not.toBe(oldValue); }); it("empties this.allLatLngLookup", () => { const points = getPoints(); const oldValue = points.allLatLngLookup; points.resetVertices(); expect(points.allLatLngLookup).not.toBe(oldValue); }); it("empties this.vertices", () => { const points = getPoints(); const oldValue = points.vertices; points.resetVertices(); expect(points.vertices).not.toBe(oldValue); }); describe("when no color is returned", () => { it("throws", () => { const points = getPoints(); points.settings.color = null; expect(() => { points.resetVertices(); }).toThrow("color is not properly defined"); }); }); describe("when no size is returned", () => { it("throws", () => { const points = getPoints(); points.settings.size = null; expect(() => { points.resetVertices(); }).toThrow("size is not properly defined"); }); }); describe('when this.dataFormat is "Array"', () => { describe("when settings.color is a function", () => { it("calls and uses the result in vertices and projects", () => { const color = jest.fn((i: number, latlng: LatLng) => { return { r: 2, g: 3, b: 4, a: 5, }; }); const points = getPoints({ data: [[1, 2]], color }); expect(points.dataFormat).toBe("Array"); color.mockClear(); points.resetVertices(); expect(color).toHaveBeenCalledWith(0, new LatLng(1, 2)); expect(points.vertices.slice(2, 6)).toEqual([2, 3, 4, 5]); }); }); describe("when settings.color is a color", () => { it("uses the color in vertices and projects", () => { const color = { r: 2, g: 3, b: 4, a: 5, }; const points = getPoints({ data: [[1, 2]], color }); expect(points.dataFormat).toBe("Array"); points.resetVertices(); expect(points.vertices.slice(2, 6)).toEqual([2, 3, 4, 5]); }); }); describe("when settings.size is a function", () => { it("calls and uses the result", () => { const size = jest.fn((i: number, latlng: LatLng | null) => { return 6; }); const points = getPoints({ data: [[1, 2]], size }); expect(points.dataFormat).toBe("Array"); size.mockClear(); points.resetVertices(); expect(size).toHaveBeenCalledWith(0, new LatLng(1, 2)); expect(points.vertices[6]).toEqual(6); }); }); describe("when settings.size is a number", () => { it("calls and uses the result", () => { const size = 6; const points = getPoints({ data: [[1, 2]], size }); expect(points.dataFormat).toBe("Array"); points.resetVertices(); expect(points.vertices[6]).toEqual(6); }); }); it("projects", () => { const points = getPoints({ data: [[1, 2]] }); expect(points.dataFormat).toBe("Array"); const project = (points.map.project = jest.fn( (latlng: LatLng, zoom: number) => { return new Point(0, 1); } )); points.resetVertices(); const expectedLatlng = new LatLng(1, 2); expect(project).toHaveBeenCalledWith(expectedLatlng, 0); }); it("calls this.addLookup with vertex", () => { const color = { r: 1, g: 2, b: 3, a: 4, }; const size = 6; const points = getPoints({ data: [[1, 2]], color, size }); const addLookupSpy = jest.spyOn(points, "addLookup"); points.map.project = jest.fn((latlng: LatLng, zoom: number) => { return new Point(0, 1); }); points.resetVertices(); const expected: IPointVertex = { latLng: new LatLng(1, 2), key: "1.00x2.00", pixel: new Point(0, 1), chosenColor: color, chosenSize: size, feature: [1, 2], }; expect(addLookupSpy).toHaveBeenCalledWith(expected); }); describe("when this.settings.eachVertex is defined", () => { it("is called with vertex", () => { const color = { r: 1, g: 2, b: 3, a: 4, }; const size = 6; const eachVertex = jest.fn((vertex: IPointVertex) => {}); const points = getPoints({ data: [[1, 2]], color, size, eachVertex }); expect(points.dataFormat).toEqual("Array"); points.map.project = jest.fn((latlng: LatLng, zoom: number) => { return new Point(0, 1); }); points.resetVertices(); const expected: IPointVertex = { latLng: new LatLng(1, 2), key: "1.00x2.00", pixel: new Point(0, 1), chosenColor: color, chosenSize: size, feature: [1, 2], }; expect(eachVertex).toHaveBeenCalledWith(expected); }); }); }); describe('when this.dataFormat is "GeoJson.FeatureCollection"', () => { const geoJson: FeatureCollection<GeoPoint> = { type: "FeatureCollection", features: [ { type: "Feature", geometry: { type: "Point", coordinates: [1, 2], }, properties: { id: 1, }, }, ], }; describe("when settings.color is a function", () => { it("calls and uses the result in vertices and projects", () => { const color = jest.fn((i: number, latlng: LatLng) => { return { r: 2, g: 3, b: 4, a: 5, }; }); const points = getPoints({ data: geoJson, color }); expect(points.dataFormat).toBe("GeoJson.FeatureCollection"); color.mockClear(); points.resetVertices(); expect(color).toHaveBeenCalledWith(0, geoJson.features[0]); expect(points.vertices.slice(2, 6)).toEqual([2, 3, 4, 5]); }); }); describe("when settings.color is a color", () => { it("uses the color in vertices and projects", () => { const color = { r: 2, g: 3, b: 4, a: 5, }; const points = getPoints({ data: geoJson, color }); expect(points.dataFormat).toBe("GeoJson.FeatureCollection"); points.resetVertices(); expect(points.vertices.slice(2, 6)).toEqual([2, 3, 4, 5]); }); }); describe("when settings.size is a function", () => { it("calls and uses the result", () => { const size = jest.fn((i: number, latlng: LatLng | null) => { return 6; }); const points = getPoints({ data: geoJson, size }); expect(points.dataFormat).toBe("GeoJson.FeatureCollection"); size.mockClear(); points.resetVertices(); expect(size).toHaveBeenCalledWith(0, new LatLng(1, 2)); expect(points.vertices[6]).toEqual(6); }); }); describe("when settings.size is a number", () => { it("calls and uses the result", () => { const size = 6; const points = getPoints({ data: geoJson, size }); expect(points.dataFormat).toBe("GeoJson.FeatureCollection"); points.resetVertices(); expect(points.vertices[6]).toEqual(6); }); }); it("projects", () => { const points = getPoints({ data: geoJson }); expect(points.dataFormat).toBe("GeoJson.FeatureCollection"); const project = (points.map.project = jest.fn( (latlng: LatLng, zoom: number) => { return new Point(0, 1); } )); points.resetVertices(); const expectedLatlng = new LatLng(1, 2); expect(project).toHaveBeenCalledWith(expectedLatlng, 0); }); it("calls this.addLookup with vertex", () => { const color = { r: 1, g: 2, b: 3, a: 4, }; const size = 6; const points = getPoints({ data: geoJson, color, size }); expect(points.dataFormat).toEqual("GeoJson.FeatureCollection"); const addLookupSpy = jest.spyOn(points, "addLookup"); points.map.project = jest.fn((latlng: LatLng, zoom: number) => { return new Point(0, 1); }); points.resetVertices(); const expected: IPointVertex = { latLng: new LatLng(1, 2), key: "1.00x2.00", pixel: new Point(0, 1), chosenColor: color, chosenSize: size, feature: geoJson.features[0], }; expect(addLookupSpy).toHaveBeenCalledWith(expected); }); describe("when this.settings.eachVertex is defined", () => { it("is called with vertex", () => { const color = { r: 1, g: 2, b: 3, a: 4, }; const size = 6; const eachVertex = jest.fn((vertex: IPointVertex) => {}); const points = getPoints({ data: geoJson, color, size, eachVertex }); points.map.project = jest.fn((latlng: LatLng, zoom: number) => { return new Point(0, 1); }); points.resetVertices(); const expected: IPointVertex = { latLng: new LatLng(1, 2), key: "1.00x2.00", pixel: new Point(0, 1), chosenColor: color, chosenSize: size, feature: geoJson.features[0], }; expect(eachVertex).toHaveBeenCalledWith(expected); }); }); }); }); describe("drawOnCanvas", () => { const event: ICanvasOverlayDrawEvent = { canvas: document.createElement("canvas"), bounds: new LatLngBounds(new LatLng(1, 2), new LatLng(1, 2)), offset: new Point(0, 0), scale: 1, size: new Point(1, 1), zoomScale: 1, zoom: 1, }; describe("when this.gl is falsey", () => { it("returns early", () => { const points = getPoints(); // @ts-expect-error in case webgl throws or is incompatible delete points.gl; expect(points.drawOnCanvas(event)).toEqual(points); }); }); describe("when this.gl is truthy", () => { it("calls this.mapMatrix correctly", () => { const points = getPoints(); const { mapMatrix } = points; jest.spyOn(points.map, "getZoom").mockReturnValue(1); const setSizeSpy = jest.spyOn(mapMatrix, "setSize"); const scaleToSpy = jest.spyOn(mapMatrix, "scaleTo"); const translateToSpy = jest.spyOn(mapMatrix, "translateTo"); points.drawOnCanvas(event); expect(setSizeSpy).toHaveBeenCalledWith(0, 0); expect(scaleToSpy).toHaveBeenCalledWith(2); expect(translateToSpy).toHaveBeenCalledWith(-0, -0); }); it("calls this.gl correctly", () => { const points = getPoints(); const { gl, canvas, matrix, mapMatrix, allLatLngLookup } = points; const clearSpy = jest.spyOn(gl, "clear"); const viewportSpy = jest.spyOn(gl, "viewport"); const uniformMatrix4fvSpy = jest.spyOn(gl, "uniformMatrix4fv"); const drawArraysSpy = jest.spyOn(gl, "drawArrays"); points.drawOnCanvas(event); expect(clearSpy).toHaveBeenCalledWith(gl.COLOR_BUFFER_BIT); expect(viewportSpy).toHaveBeenCalledWith( 0, 0, canvas.width, canvas.height ); expect(uniformMatrix4fvSpy).toHaveBeenCalledWith( matrix, false, mapMatrix.array ); expect(drawArraysSpy).toHaveBeenCalledWith( gl.POINTS, 0, allLatLngLookup.length ); }); }); }); describe("lookup", () => { let closestSpy: jest.SpyInstance; beforeEach(() => { closestSpy = jest.spyOn(Points, "closest"); }); describe("when matches", () => { it("calls Points.closest with coords and matches", () => { const points = getPoints({ data: [] }); const pointVertex: IPointVertex = { latLng: new LatLng(0, 0), pixel: { x: 0, y: 0, }, chosenColor: { r: 1, g: 1, b: 1, a: 1, }, chosenSize: 1, key: "key", feature: {}, }; points.latLngLookup = { "-0.03x-0.03": [pointVertex], }; const coords = new LatLng(0, 0); points.lookup(coords); expect(closestSpy).toHaveBeenCalledWith( coords, [pointVertex], points.map ); }); }); describe("when not matches", () => { it("calls Points.closest with coords and matches", () => { const points = getPoints({ data: [] }); const pointVertex: IPointVertex = { latLng: new LatLng(0, 0), pixel: { x: 0, y: 0, }, chosenColor: { r: 1, g: 1, b: 1, a: 1, }, chosenSize: 1, key: "key", feature: {}, }; points.allLatLngLookup.push(pointVertex); const coords = new LatLng(0, 0); points.lookup(coords); expect(closestSpy).toHaveBeenCalledWith( coords, points.allLatLngLookup, points.map ); }); }); }); // TODO: tryClick // TODO: tryHover });