Adding some first unit test and managing translations with scale=1

This commit is contained in:
Eric van der Vlist 2022-09-14 17:50:27 +02:00
parent 3a7d4987a3
commit 67e4d13989
4 changed files with 129 additions and 7 deletions

View File

@ -20,6 +20,7 @@ const Map: react.FC<{}> = (props: {}) => {
useEffect(() => {
window.addEventListener('resize', debouncedResizeHandler);
// dispatch(mapActions.shift({ x: -50, y: 0 }));
}, []);
return (

14
src/setupTests.ts Normal file
View File

@ -0,0 +1,14 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
// Mock matchmedia
window.matchMedia = window.matchMedia || function() {
return {
matches: false,
addListener: function() {},
removeListener: function() {}
};
};

69
src/store/map.test.ts Normal file
View File

@ -0,0 +1,69 @@
import _ from 'lodash';
import { initialMapState, reevaluateState } from './map';
//interface CustomMatchers<R = unknown> {
// isDeepEqual(received: any, target: any): R;
//}
//declare global {
// namespace jest {
// interface Expect extends CustomMatchers {}
// interface Matchers<R> extends CustomMatchers<R> {}
// interface InverseAsymmetricMatchers extends CustomMatchers {}
// }
//}
expect.extend({
isDeepEqual: (received: any, target: any) => {
const pass = _.isEqual(received, target);
if (pass) {
return { message: () => '', pass: true };
}
return {
message: () =>
`${JSON.stringify(received)} instead of\n ${JSON.stringify(target)}`,
pass: false,
};
},
isAlmostDeepEqual: (received: any, target: any, delta: number) => {
const pass = _.isEqualWith(received, target, (r: any, t: any) => {
if (typeof r !== 'number' || typeof t !== 'number') {
return undefined;
}
console.log(`r: ${r}, t:${t}, ${Math.abs(r - t) <= delta}`);
return Math.abs(r - t) <= delta;
});
if (pass) {
return { message: () => '', pass: true };
}
return {
message: () =>
`${JSON.stringify(received)} instead of\n ${JSON.stringify(target)}`,
pass: false,
};
},
});
describe('Our isAlmostDeepEqual matcher', () => {
test('compares correctly two numbers that are almost equals', () => {
expect({ x: 1.001 }).isAlmostDeepEqual({ x: 1 }, 1e-2);
});
});
describe('Map store methods', () => {
test('initialize its state', () => {
const state = _.cloneDeep(initialMapState);
expect(state.tiles.nb.x).not.toBe(0);
});
test('reevaluateState keeps the same values', () => {
const state = _.cloneDeep(initialMapState);
reevaluateState(state);
expect(state).isAlmostDeepEqual(initialMapState, 1e-7);
});
test('reevaluateState computes the right longitude after a shift 50 pixels left', () => {
const state = _.cloneDeep(initialMapState);
state.slippy.translation.x = state.slippy.translation.x - 50;
reevaluateState(state);
expect(state.scope.center.lon).not.toBe(77.5539501);
});
});

View File

@ -1,7 +1,14 @@
import { createSlice } from '@reduxjs/toolkit';
import _ from 'lodash';
import { tileSize } from '../components/map/tiled-map';
import { geoPoint, Point, lon2tile, lat2tile } from '../lib/geo';
import {
geoPoint,
Point,
lon2tile,
lat2tile,
tile2lat,
tile2long,
} from '../lib/geo';
// Top level properties (the other properties can be derived from them)
@ -12,7 +19,7 @@ export interface MapScope {
}
const initialMapScope: MapScope = {
center: { lat: -37.8403508, lon: 77.5539501 },
zoom: 13.49,
zoom: 13,
};
// Derived properties
@ -60,7 +67,7 @@ const nbTilesY = _.ceil(mapState.viewport.height / tileSize) + 4;
*/
let initialMapState: MapState = {
export var initialMapState: MapState = {
scope: initialMapScope,
slippy: {
scale: 1,
@ -82,7 +89,8 @@ let initialMapState: MapState = {
},
};
const evaluateStateFromScope = (state: MapState) => {
export const evaluateStateFromScope = (state: MapState) => {
console.log('<<<<<<<<<<<< evaluateStateFromScope');
state.tiles.zoom = _.round(state.scope.zoom);
const softZoom = state.scope.zoom - state.tiles.zoom;
state.slippy.scale = 2 ** softZoom;
@ -115,9 +123,37 @@ const evaluateStateFromScope = (state: MapState) => {
evaluateStateFromScope(initialMapState);
const reevaluateState = (state: MapState) => {};
export const reevaluateState = (state: MapState) => {
// Update the scope (center and zoom level)
const centerPX = {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
};
const visibleTileSize = tileSize / state.slippy.scale;
const centerTiles = {
x:
state.tiles.first.x +
(centerPX.x - state.slippy.translation.x) / visibleTileSize,
y:
state.tiles.first.y +
(centerPX.y - state.slippy.translation.y) / visibleTileSize,
};
state.scope.center = {
lat: tile2lat(centerTiles.y, state.tiles.zoom),
lon: tile2long(centerTiles.x, state.tiles.zoom),
};
// TODO: zoom level
if (
-state.slippy.translation.x < visibleTileSize ||
-state.slippy.translation.y < visibleTileSize ||
-state.slippy.translation.x >
(state.tiles.nb.x - 1) * visibleTileSize - window.innerWidth ||
-state.slippy.translation.y >
(state.tiles.nb.y - 1) * visibleTileSize - window.innerHeight
) {
evaluateStateFromScope(state);
}
};
const mapSlice = createSlice({
name: 'map',
@ -131,6 +167,7 @@ const mapSlice = createSlice({
x: state.slippy.translation.x + action.payload.x,
y: state.slippy.translation.y + action.payload.y,
};
reevaluateState(state);
},
scale: (state, action) => {
state.slippy.scale = state.slippy.scale * action.payload.factor;
@ -144,6 +181,7 @@ const mapSlice = createSlice({
(state.slippy.translation.y - action.payload.center.y) *
(action.payload.factor - 1),
};
reevaluateState(state);
},
},
});