leaflet.freedraw
Version:
Zoopla inspired freehand polygon creation using Leaflet.js.
226 lines (168 loc) • 7.09 kB
JavaScript
import test from 'ava';
import { select } from 'd3-selection';
import { LatLng, Point } from 'leaflet';
import { spy } from 'sinon';
import FreeDraw, { polygons, edgesKey } from '../src/FreeDraw';
import { updateFor } from '../src/helpers/Layer';
import { NONE, CREATE, EDIT, DELETE, APPEND, EDIT_APPEND, ALL } from '../src/helpers/Flags';
/**
* @method mockFunctions
* @param {Object} map
* @return {void}
*/
function mockFunctions(map) {
map.simplifyPolygon = (map, latLngs) => [latLngs];
map.addLayer = spy();
map.removeLayer = spy();
}
/**
* @constant polygon
* @type {Object}
*/
const polygon = [new LatLng(51.50249181873096, -0.08634567260742189),
new LatLng(51.50281238523426, -0.09501457214355469),
new LatLng(51.50799456412721, -0.09441375732421875),
new LatLng(51.509062981334914, -0.08428573608398438),
new LatLng(51.50249181873096, -0.08634567260742189)];
test.beforeEach(t => {
const node = t.context.node = document.createElement('div');
t.context.map = {
on: spy(),
fire: spy(),
dragging: {
disable: spy(),
enable: spy()
},
_container: node,
latLngToLayerPoint: spy(({ lat, lng }) => ({ x: lat, y: lng })),
layerPointToLatLng: spy(({ x, y }) => ({ lat: x, lng: y }))
};
t.context.svg = select(node).append('svg');
t.context.polygon = [...polygon];
t.context.freeDraw = new FreeDraw({ mergePolygons: false, concavePolygon: false });
});
test('It should be able to create the map instance;', t => {
const { freeDraw, map, node } = t.context;
freeDraw.fire = spy();
freeDraw.listenForEvents = spy();
freeDraw.onAdd(map);
// Ensure the polygons contains the map instance, which will eventually store the polygons.
t.truthy(polygons.has(map));
// Ensure the mode has been set by default to `ALL`.
t.is(freeDraw.mode(), ALL);
// Ensure the initial event for the modes was invoked.
t.is(freeDraw.fire.callCount, 1);
t.truthy(freeDraw.fire.calledWith('mode', { mode: ALL }));
// Ensure D3 has successfully appended the SVG node.
// Twice because the `beforeEach` also appends a SVG node.
t.is(node.querySelectorAll('svg').length, 2);
// Ensure the `listenForEvent` function was invoked.
t.is(freeDraw.listenForEvents.callCount, 1);
});
test('It should be able to create polygons;', t => {
const { freeDraw, map, polygon } = t.context;
freeDraw.onAdd(map);
mockFunctions(map);
// ...And then invoke the `create` function!
freeDraw.create(polygon, true);
// Ensure the expected functions are invoked.
t.truthy(map.addLayer.called);
t.is(map.latLngToLayerPoint.callCount, polygon.length - 1);
// Ensure it's correctly added to the `polygons` set.
t.is(polygons.get(map).size, 1);
});
test('It should be able to remove polygons;', t => {
const { freeDraw, map, polygon } = t.context;
freeDraw.onAdd(map);
mockFunctions(map);
// Invoke the `create` function.
const [firstPolygon] = freeDraw.create(polygon, true);
// Ensure it's correctly added to the `polygons` set, as well as the associated edges.
t.is(polygons.get(map).size, 1);
t.is(firstPolygon[edgesKey].length, 4);
// ... And then remove it immediately.
freeDraw.remove(firstPolygon);
// Ensure it's correctly removed from the `polygons` set.
t.is(polygons.get(map).size, 0);
// Ensure the expected functions are invoked.
t.truthy(map.removeLayer.called);
});
test('It should be able to clear polygons;', t => {
const { freeDraw, map, polygon } = t.context;
freeDraw.onAdd(map);
mockFunctions(map);
// Invoke the `create` function.
freeDraw.create(polygon, true);
freeDraw.create(polygon, true);
freeDraw.create(polygon, true);
// Ensure it's correctly added to the `polygons` set.
t.is(polygons.get(map).size, 3);
// ... And then clear them all.
freeDraw.clear();
t.is(polygons.get(map).size, 0);
});
test('It should be able to trigger events on the map instance;', t => {
const { freeDraw, map, polygon } = t.context;
freeDraw.fire = spy();
freeDraw.onAdd(map);
mockFunctions(map);
// Invoke the `create` function.
const [firstPolygon] = freeDraw.create(polygon, true);
// Ensure the `fire` method has been invoked with the correct parameters.
updateFor(map);
const closedPolygon = [...firstPolygon.getLatLngs()[0], firstPolygon.getLatLngs()[0][0]];
t.truthy(freeDraw.fire.calledWith('markers', { latLngs: [closedPolygon] }));
});
test('It should be able to clear up once removed;', t => {
const { freeDraw, map } = t.context;
freeDraw.onAdd(map);
mockFunctions(map);
// Map should be contained within the WeakMap.
t.truthy(polygons.has(map));
// But should be gone once removed.
freeDraw.onRemove(map);
t.falsy(polygons.has(map));
});
test('It should be able to set the mode on the map;', t => {
const { freeDraw, map, node } = t.context;
freeDraw.onAdd(map);
// Ensure default mode is correctly set, with all of the relevant class names.
t.is(freeDraw.mode(), ALL);
t.truthy(node.classList.contains('mode-create'));
t.truthy(node.classList.contains('mode-edit'));
t.truthy(node.classList.contains('mode-delete'));
t.truthy(node.classList.contains('mode-append'));
// Update the mode to NONE only.
const firstMode = freeDraw.mode(NONE);
t.is(firstMode, NONE);
t.is(freeDraw.mode(), NONE);
t.falsy(node.classList.contains('mode-create'));
t.falsy(node.classList.contains('mode-edit'));
t.falsy(node.classList.contains('mode-delete'));
t.falsy(node.classList.contains('mode-append'));
// Update to CREATE and EDIT only.
const secondMode = freeDraw.mode(CREATE | EDIT);
t.is(secondMode, CREATE | EDIT);
t.is(freeDraw.mode(), CREATE | EDIT);
t.truthy(node.classList.contains('mode-create'));
t.truthy(node.classList.contains('mode-edit'));
t.falsy(node.classList.contains('mode-delete'));
t.falsy(node.classList.contains('mode-append'));
});
test('It should be able to create a path;', t => {
const { freeDraw, map, svg } = t.context;
freeDraw.onAdd(map);
// Initiate the generator and move it to the first `yield`.
const iterator = freeDraw.createPath(map, svg, new Point(20, 20));
iterator.next();
// Draw a couple of lines and ensure the returns are correct.
const firstLine = iterator.next(new Point(40, 40));
t.deepEqual(firstLine, { value: { x: 40, y: 40 }, done: false });
t.is(svg.selectAll('path').size(), 1);
const secondLine = iterator.next(new Point(60, 60));
t.deepEqual(secondLine, { value: { x: 60, y: 60 }, done: false });
t.is(svg.selectAll('path').size(), 2);
const thirdLine = iterator.next(new Point(120, 120));
t.deepEqual(thirdLine, { value: { x: 120, y: 120 }, done: false });
t.is(svg.selectAll('path').size(), 3);
});