Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 232 additions & 36 deletions src/algorithms/superviewport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SuperClusterViewportAlgorithm } from "./superviewport";
import { initialize, MapCanvasProjection } from "@googlemaps/jest-mocks";
import { Marker } from "../marker-utils";
import { ClusterFeature } from "supercluster";
import { Cluster } from "../cluster";

initialize();
const markerClasses = [
Expand All @@ -29,13 +30,28 @@ describe.each(markerClasses)(
"SuperCluster works with legacy and Advanced Markers",
(markerClass) => {
let map: google.maps.Map;
let mapCanvasProjection: MapCanvasProjection;

beforeEach(() => {
map = new google.maps.Map(document.createElement("div"));
mapCanvasProjection = new MapCanvasProjection();

mapCanvasProjection.fromLatLngToDivPixel = jest
.fn()
.mockImplementation((latLng: google.maps.LatLng) => ({
x: latLng.lng() * 100,
y: latLng.lat() * 100,
}));

mapCanvasProjection.fromDivPixelToLatLng = jest
.fn()
.mockImplementation((point: google.maps.Point) => ({
lat: () => point.y / 100,
lng: () => point.x / 100,
}));
});

test("should only call load if markers change", () => {
const mapCanvasProjection = new MapCanvasProjection();
const markers: Marker[] = [new markerClass()];

const superCluster = new SuperClusterViewportAlgorithm({});
Expand Down Expand Up @@ -63,25 +79,31 @@ describe.each(markerClasses)(
});

test("should cluster markers", () => {
const mapCanvasProjection = new MapCanvasProjection();
const markers: Marker[] = [new markerClass(), new markerClass()];

const superCluster = new SuperClusterViewportAlgorithm({});
map.getZoom = jest.fn().mockReturnValue(0);

const northEast = {
lat: jest.fn().mockReturnValue(-3),
lng: jest.fn().mockReturnValue(34),
};
const southWest = {
lat: jest.fn().mockReturnValue(29),
lng: jest.fn().mockReturnValue(103),
};

map.getBounds = jest.fn().mockReturnValue({
toJSON: () => ({
west: -180,
south: -90,
east: 180,
north: 90,
}),
getNorthEast: jest
.fn()
.mockReturnValue({ getLat: () => -3, getLng: () => 34 }),
getSouthWest: jest
.fn()
.mockReturnValue({ getLat: () => 29, getLng: () => 103 }),
getNorthEast: jest.fn().mockReturnValue(northEast),
getSouthWest: jest.fn().mockReturnValue(southWest),
});

const { clusters } = superCluster.calculate({
markers,
map,
Expand All @@ -106,7 +128,6 @@ describe.each(markerClasses)(
},
};

// mock out the supercluster implementation
jest
.spyOn(superCluster["superCluster"], "getLeaves")
.mockImplementation(() => [clusterFeature]);
Expand All @@ -117,7 +138,6 @@ describe.each(markerClasses)(
});

test("should not cluster if zoom didn't change", () => {
const mapCanvasProjection = new MapCanvasProjection();
const markers: Marker[] = [new markerClass(), new markerClass()];

const superCluster = new SuperClusterViewportAlgorithm({});
Expand All @@ -134,12 +154,11 @@ describe.each(markerClasses)(
mapCanvasProjection,
});

expect(changed).toBeTruthy();
expect(changed).toBeFalsy();
expect(clusters).toBe(superCluster["clusters"]);
});

test("should not cluster if zoom beyond maxZoom", () => {
const mapCanvasProjection = new MapCanvasProjection();
const markers: Marker[] = [new markerClass(), new markerClass()];

const superCluster = new SuperClusterViewportAlgorithm({});
Expand All @@ -150,40 +169,46 @@ describe.each(markerClasses)(

map.getZoom = jest.fn().mockReturnValue(superCluster["state"].zoom + 1);

const northEast = {
lat: jest.fn().mockReturnValue(0),
lng: jest.fn().mockReturnValue(0),
};
const southWest = {
lat: jest.fn().mockReturnValue(0),
lng: jest.fn().mockReturnValue(0),
};
map.getBounds = jest.fn().mockReturnValue({
getNorthEast: jest.fn().mockReturnValue(northEast),
getSouthWest: jest.fn().mockReturnValue(southWest),
});

const { clusters, changed } = superCluster.calculate({
markers,
map,
mapCanvasProjection,
});

expect(changed).toBeTruthy();
expect(changed).toBeFalsy();
expect(clusters).toBe(superCluster["clusters"]);
expect(superCluster["state"]).toEqual({ zoom: 21, view: [0, 0, 0, 0] });
expect(superCluster["state"].zoom).toBe(21);
expect(Array.isArray(superCluster["state"].view)).toBeTruthy();
});

test("should round fractional zoom", () => {
const mapCanvasProjection = new MapCanvasProjection();
const markers: Marker[] = [new markerClass(), new markerClass()];
mapCanvasProjection.fromLatLngToDivPixel = jest
.fn()
.mockImplementation((b: google.maps.LatLng) => ({
x: b.lat() * 100,
y: b.lng() * 100,
}));
mapCanvasProjection.fromDivPixelToLatLng = jest
.fn()
.mockImplementation(
(p: google.maps.Point) =>
new google.maps.LatLng({ lat: p.x / 100, lng: p.y / 100 })
);

const northEast = {
lat: jest.fn().mockReturnValue(-3),
lng: jest.fn().mockReturnValue(34),
};
const southWest = {
lat: jest.fn().mockReturnValue(29),
lng: jest.fn().mockReturnValue(103),
};

map.getBounds = jest.fn().mockReturnValue({
getNorthEast: jest
.fn()
.mockReturnValue({ lat: () => -3, lng: () => 34 }),
getSouthWest: jest
.fn()
.mockReturnValue({ lat: () => 29, lng: () => 103 }),
getNorthEast: jest.fn().mockReturnValue(northEast),
getSouthWest: jest.fn().mockReturnValue(southWest),
});

const superCluster = new SuperClusterViewportAlgorithm({});
Expand All @@ -195,19 +220,190 @@ describe.each(markerClasses)(
map.getZoom = jest.fn().mockReturnValue(1.534);
expect(
superCluster.calculate({ markers, map, mapCanvasProjection })
).toEqual({ changed: true, clusters: [] });
).toEqual({ changed: false, clusters: [] });

expect(superCluster["superCluster"].getClusters).toHaveBeenCalledWith(
[0, 0, 0, 0],
expect.any(Array),
2
);

map.getZoom = jest.fn().mockReturnValue(3.234);
superCluster.calculate({ markers, map, mapCanvasProjection });
expect(superCluster["superCluster"].getClusters).toHaveBeenCalledWith(
[0, 0, 0, 0],
expect.any(Array),
3
);
});

test("should return changed=false when viewport changes but clusters remain the same", () => {
const markers: Marker[] = [new markerClass(), new markerClass()];

map.getZoom = jest.fn().mockReturnValue(10);

const initialNorthEast = {
lat: jest.fn().mockReturnValue(10),
lng: jest.fn().mockReturnValue(10),
};
const initialSouthWest = {
lat: jest.fn().mockReturnValue(0),
lng: jest.fn().mockReturnValue(0),
};
const initialBounds = {
getNorthEast: jest.fn().mockReturnValue(initialNorthEast),
getSouthWest: jest.fn().mockReturnValue(initialSouthWest),
};
map.getBounds = jest.fn().mockReturnValue(initialBounds);

const algorithm = new SuperClusterViewportAlgorithm({
viewportPadding: 60,
});

const sameCluster = [
new Cluster({
markers: markers,
position: { lat: 5, lng: 5 },
}),
];

algorithm.cluster = jest.fn().mockReturnValue(sameCluster);

const firstResult = algorithm.calculate({
markers,
map,
mapCanvasProjection,
});

expect(firstResult.changed).toBeTruthy();

const newNorthEast = {
lat: jest.fn().mockReturnValue(15),
lng: jest.fn().mockReturnValue(15),
};
const newSouthWest = {
lat: jest.fn().mockReturnValue(5),
lng: jest.fn().mockReturnValue(5),
};
const newBounds = {
getNorthEast: jest.fn().mockReturnValue(newNorthEast),
getSouthWest: jest.fn().mockReturnValue(newSouthWest),
};
map.getBounds = jest.fn().mockReturnValue(newBounds);

const secondResult = algorithm.calculate({
markers,
map,
mapCanvasProjection,
});

expect(secondResult.changed).toBeFalsy();
expect(secondResult.clusters).toEqual(sameCluster);
});

test("should detect cluster changes accurately with areClusterArraysEqual", () => {
const markers: Marker[] = [new markerClass(), new markerClass()];

map.getZoom = jest.fn().mockReturnValue(10);
map.getBounds = jest.fn().mockReturnValue({
getNorthEast: jest
.fn()
.mockReturnValue({ lat: () => 10, lng: () => 10 }),
getSouthWest: jest.fn().mockReturnValue({ lat: () => 0, lng: () => 0 }),
});

const algorithm = new SuperClusterViewportAlgorithm({});

const cluster1 = [
new Cluster({
markers: [markers[0]],
position: { lat: 2, lng: 2 },
}),
new Cluster({
markers: [markers[1]],
position: { lat: 8, lng: 8 },
}),
];

algorithm.cluster = jest.fn().mockReturnValueOnce(cluster1);

const result1 = algorithm.calculate({
markers,
map,
mapCanvasProjection,
});
expect(result1.changed).toBeTruthy();

const cluster2 = [
new Cluster({
markers: [markers[0]],
position: { lat: 2, lng: 2 },
}),
new Cluster({
markers: [markers[1]],
position: { lat: 8, lng: 8 },
}),
];

algorithm.cluster = jest.fn().mockReturnValueOnce(cluster2);

const result2 = algorithm.calculate({
markers,
map,
mapCanvasProjection,
});
expect(result2.changed).toBeFalsy();

const cluster3 = [
new Cluster({
markers: markers,
position: { lat: 5, lng: 5 },
}),
];

algorithm.cluster = jest.fn().mockReturnValueOnce(cluster3);

const result3 = algorithm.calculate({
markers,
map,
mapCanvasProjection,
});
expect(result3.changed).toBeTruthy();
});

test("should correctly calculate viewport state with getPaddedViewport", () => {
const markers: Marker[] = [new markerClass()];

map.getZoom = jest.fn().mockReturnValue(10);

const northEast = {
lat: jest.fn().mockReturnValue(10),
lng: jest.fn().mockReturnValue(20),
};
const southWest = {
lat: jest.fn().mockReturnValue(0),
lng: jest.fn().mockReturnValue(10),
};

const bounds = {
getNorthEast: jest.fn().mockReturnValue(northEast),
getSouthWest: jest.fn().mockReturnValue(southWest),
};
map.getBounds = jest.fn().mockReturnValue(bounds);

const algorithm = new SuperClusterViewportAlgorithm({
viewportPadding: 60,
});
algorithm.cluster = jest.fn().mockReturnValue([]);

algorithm.calculate({
markers,
map,
mapCanvasProjection,
});

const state = algorithm["state"];

expect(state.view).toEqual([0, 0, 0, 0]);
expect(state.zoom).toBe(10);
});
}
);
Loading