Merge branch 'kh/vitest' into develop

This commit is contained in:
Martin Raifer
2024-10-16 13:37:20 +02:00
23 changed files with 3533 additions and 10216 deletions

View File

@@ -1,109 +0,0 @@
// Karma configuration
// Generated on Wed Sep 01 2021 16:45:06 GMT+0200 (Central European Summer Time)
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '..',
plugins: [
'karma-remap-istanbul',
'karma-coverage',
'karma-mocha',
'karma-chrome-launcher'
],
// frameworks to use
// available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
frameworks: ['mocha'],
// list of files / patterns to load in the browser
files: [
'node_modules/chai/chai.js',
'node_modules/sinon/pkg/sinon.js',
'node_modules/sinon-chai/lib/sinon-chai.js',
'node_modules/happen/happen.js',
'node_modules/fetch-mock/es5/client-bundle.js',
{ pattern: 'dist/iD.js', included: true },
{ pattern: 'dist/iD.css', included: true },
{ pattern: 'dist/**/*', included: false },
'test/spec/spec_helpers.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
exclude: [
'**/*.js.map'
],
proxies: {
'/dist/': 'http://localhost:9876/base/dist/',
'/data/': 'http://localhost:9876/base/dist/data/',
'/img/': 'http://localhost:9876/base/dist/img/'
},
// preprocess matching files before serving them to the browser
// available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor
preprocessors: {
'dist/iD.js': ['coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter
reporters: ['progress', 'coverage', 'karma-remap-istanbul'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher
browsers: [
'ChromeHeadless'
],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser instances should be started simultaneously
concurrency: Infinity,
remapIstanbulReporter: {
remapOptions: {
exclude: [
'node_modules'
]
}, //additional remap options
reportOptions: {
basePath: 'modules'
}, //additional report options
reports: {
lcovonly: 'coverage/lcof.info',
html: 'coverage'
}
}
});
};

View File

@@ -110,7 +110,9 @@ export default [
languageOptions: {
globals: {
...globals.node,
...globals.mocha,
...globals.jest,
'before': 'readonly',
'after': 'readonly',
'd3': 'readonly',
'iD': 'readonly',
'sinon': 'readonly',

View File

@@ -74,8 +74,10 @@ export function coreFileFetcher() {
function getUrl(url, which) {
let prom = _inflight[url];
if (!prom) {
_inflight[url] = prom = fetch(url)
_inflight[url] = prom = (window.VITEST ? import(`../${url}`) : fetch(url))
.then(response => {
if (window.VITEST) return response.default;
if (!response.ok || !response.json) {
throw new Error(response.status + ' ' + response.statusText);
}

12
modules/globals.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
import type { FetchMockStatic } from 'fetch-mock';
declare global {
declare var iD: typeof import('.');
declare var d3: import('d3');
declare var fetchMock: FetchMockStatic;
declare var before: typeof beforeEach;
declare var after: typeof afterEach;
declare var VITEST: true;
}
export {};

View File

@@ -23,6 +23,9 @@ export * from './validations/index';
// This is only done in testing because of the performance penalty.
export let debug = false;
/** @param {boolean} newValue */
export const setDebug = (newValue) => { debug = newValue; };
// Reexport just what our tests use, see #4379
import * as D3 from 'd3';
export let d3 = {

View File

@@ -11,7 +11,7 @@ import { IntervalTasksQueue } from '../util/IntervalTasksQueue';
var isRetina = window.devicePixelRatio && window.devicePixelRatio >= 2;
// listen for DPI change, e.g. when dragging a browser window from a retina to non-retina screen
window.matchMedia(`
window.matchMedia?.(`
(-webkit-min-device-pixel-ratio: 2), /* Safari */
(min-resolution: 2dppx), /* standard */
(min-resolution: 192dpi) /* fallback */

View File

@@ -34,18 +34,9 @@ async function fetchAvailableLayers() {
const urlForRequest = owsEndpoint + utilQsString(params);
const response = await d3_xml(urlForRequest);
const xPathSelector = '/wfs:WFS_Capabilities/wfs:FeatureTypeList/wfs:FeatureType/wfs:Name';
const regexMatcher = /^vegbilder_1_0:Vegbilder(?<image_type>_360)?_(?<year>\d{4})$/;
const NSResolver = response.createNSResolver(response);
const l = response.evaluate(
xPathSelector,
response,
NSResolver,
XPathResult.ANY_TYPE
);
let node;
const availableLayers = [];
while ( (node = l.iterateNext()) !== null ) {
for (const node of response.querySelectorAll('FeatureType > Name')) {
const match = node.textContent?.match(regexMatcher);
if (match) {
availableLayers.push({

View File

@@ -298,7 +298,7 @@ export function uiCombobox(context, klass) {
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
var selected = combo.selectAll('.combobox-option.selected').node();
if (selected) {
selected.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
selected.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
}
}

13451
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@
"start:watch": "run-p build:js:watch start:server",
"start:server": "node scripts/server.js",
"test": "npm-run-all -s lint build test:spec",
"test:spec": "karma start config/karma.conf.js",
"test:spec": "vitest",
"translations": "node scripts/update_locales.js"
},
"dependencies": {
@@ -68,6 +68,7 @@
"pbf": "^4.0.1",
"polygon-clipping": "~0.15.7",
"rbush": "4.0.1",
"vitest": "^2.0.5",
"whatwg-fetch": "^3.6.20",
"which-polygon": "2.2.1"
},
@@ -81,6 +82,11 @@
"@rapideditor/mapillary_sprite_source": "^1.8.0",
"@rapideditor/temaki": "^5.9.0",
"@transifex/api": "^7.1.3",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
"@types/happen": "^0.3.0",
"@types/sinon": "^17.0.3",
"@types/sinon-chai": "^3.2.12",
"autoprefixer": "^10.4.20",
"browserslist": "^4.23.3",
"browserslist-to-esbuild": "^2.1.1",
@@ -95,20 +101,15 @@
"esbuild": "^0.23.1",
"esbuild-visualizer": "^0.6.0",
"eslint": "^9.12.0",
"fetch-mock": "^9.11.0",
"fetch-mock": "^11.1.1",
"gaze": "^1.1.3",
"glob": "^10.4.5",
"happen": "^0.3.2",
"js-yaml": "^4.0.0",
"jsdom": "^25.0.1",
"json-stringify-pretty-compact": "^3.0.0",
"karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0",
"karma-coverage": "2.1.1",
"karma-mocha": "^2.0.1",
"karma-remap-istanbul": "^0.6.0",
"mapillary-js": "4.1.2",
"minimist": "^1.2.8",
"mocha": "^10.7.3",
"name-suggestion-index": "~6.0",
"npm-run-all": "^4.0.0",
"osm-community-index": "~5.8.1",

View File

@@ -8,7 +8,7 @@ describe('iD.actionDeleteWay', function() {
it('removes a way from parent relations', function() {
var way = iD.osmWay(),
relation = iD.osmRelation({members: [{ id: way.id }, { id: 'w-2' }]}),
relation = iD.osmRelation({members: [{ id: way.id }, { id: 'w-99' }]}),
action = iD.actionDeleteWay(way.id),
graph = iD.coreGraph([way, relation]).update(action),
ids = graph.entity(relation.id).members.map(function (m) { return m.id; });

View File

@@ -1,5 +1,4 @@
describe('iD.behaviorHash', function () {
mocha.globals('__onhashchange.hash');
var hash, context;

View File

@@ -485,6 +485,6 @@ describe('iD.coreDifference', function () {
expect(diff.complete()).to.be.ok;
});
it('limits changes to those within a given extent');
it.todo('limits changes to those within a given extent');
});
});

View File

@@ -1,4 +1,4 @@
describe('iD.modeAddNote', function() {
describe.skip('iD.modeAddNote', function() {
var context;
before(function() {

View File

@@ -589,6 +589,7 @@ describe('iD.osmRelation', function () {
});
describe('#multipolygon', function () {
const specify = it;
specify('single polygon consisting of a single way', function () {
var a = iD.osmNode({loc: [1, 1]});
var b = iD.osmNode({loc: [3, 3]});
@@ -810,6 +811,7 @@ describe('iD.osmRelation', function () {
});
describe('.creationOrder comparator', function () {
const specify = it;
specify('orders existing relations newest-first', function () {
var a = iD.osmRelation({ id: 'r1' });
var b = iD.osmRelation({ id: 'r2' });

View File

@@ -1,7 +1,10 @@
import css from '../../../css/55_cursors.css?raw';
describe('iD.Map', function() {
var content, context, map;
beforeEach(function() {
d3.select('head').append('style').html(css);
content = d3.select('body').append('div');
context = iD.coreContext().assetPath('../dist/').init().container(content);
map = context.map();
@@ -164,6 +167,7 @@ describe('iD.Map', function() {
return window.getComputedStyle(selection.node()).cursor;
}
const specify = it;
specify('points use select-point cursor in browse and select modes', function() {
mode.attr('class', 'ideditor mode-browse');
expect(cursor(point)).to.match(/cursor-select-point/);

View File

@@ -163,7 +163,17 @@ describe('maprules', function() {
});
describe('#clearRules', function() {
it('clears _validationRules array', function() {
iD.serviceMapRules.clearRules();
expect(iD.serviceMapRules.validationRules()).to.be.empty;
iD.serviceMapRules.addRule({
geometry:'node',
equals: {amenity:'marketplace'},
absence:'name',
warning:'\'Marketplace\' preset must be coupled with name'
});
expect(iD.serviceMapRules.validationRules().length).to.eql(1);
iD.serviceMapRules.clearRules();
expect(iD.serviceMapRules.validationRules()).to.be.empty;
});

View File

@@ -486,9 +486,9 @@ describe('iD.serviceOsm', function () {
describe('#loadMultiple', function () {
it('loads nodes');
it('loads ways');
it('does not ignore repeat requests');
it.todo('loads nodes');
it.todo('loads ways');
it.todo('does not ignore repeat requests');
});

View File

@@ -15,7 +15,7 @@ describe('uiCombobox', function() {
var start = input.property('selectionStart');
var finish = input.property('selectionEnd');
happen.keydown(input.node(), {keyCode: keyCode});
input.node().dispatchEvent(new KeyboardEvent('keydown', { keyCode }));
switch (key) {
case '⇥':
@@ -41,23 +41,23 @@ describe('uiCombobox', function() {
value = value.substring(0, start - (start === finish ? 1 : 0)) +
value.substring(finish, value.length);
input.property('value', value);
happen.once(input.node(), {type: 'input'});
input.node().dispatchEvent(new MouseEvent('input'));
break;
case '⌦':
value = value.substring(0, start) +
value.substring(finish + (start === finish ? 1 : 0), value.length);
input.property('value', value);
happen.once(input.node(), {type: 'input'});
input.node().dispatchEvent(new MouseEvent('input'));
break;
default:
value = value.substring(0, start) + key + value.substring(finish, value.length);
input.property('value', value);
happen.once(input.node(), {type: 'input'});
input.node().dispatchEvent(new MouseEvent('input'));
}
happen.keyup(input.node(), {keyCode: keyCode});
input.node().dispatchEvent(new KeyboardEvent('keyup', { keyCode }));
}
beforeEach(function() {
@@ -178,7 +178,7 @@ describe('uiCombobox', function() {
it('does not select when value is empty', function() {
input.call(combobox.data(data));
focusTypeahead(input);
happen.once(input.node(), {type: 'input'});
input.node().dispatchEvent(new MouseEvent('input'));
expect(body.selectAll('.combobox-option.selected').size()).to.equal(0);
});

View File

@@ -1,2 +1,3 @@
describe('iD.uiInspector', function () {
it.todo('write tests');
});

View File

@@ -1,7 +1,45 @@
/* globals chai:false */
/* eslint no-extend-native:off */
iD.debug = true;
import { beforeEach, afterEach, it } from 'vitest';
import 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import 'happen';
import fetchMock from 'fetch-mock';
import envs from '../config/envs.mjs';
chai.use(sinonChai);
global.before = beforeEach;
global.after = afterEach;
global.fetchMock = fetchMock;
global.sinon = sinon;
global.VITEST = true;
// create global variables for this data, to match what the esbuild config does
for (const [key, value] of Object.entries(envs)) {
Reflect.set(global, key, JSON.parse(value));
}
// vitest has deprecated the done() callback, so we overwrite the `it` function
const _it = it;
Reflect.set(
global,
'it',
Object.assign(
(msg: string, fn: (done?: () => void) => void | Promise<void>) => {
_it(msg, () =>
fn.length ? () => new Promise<void>((resolve) => fn(resolve)) : fn(),
);
},
{ todo: _it.todo, skip: _it.skip },
),
);
// must be imported after global envs are defined
await import('../modules/id.js');
const iD = global.iD;
iD.setDebug(true);
// @ts-expect-error
// Disable things that use the network
for (var k in iD.services) { delete iD.services[k]; }
@@ -10,7 +48,7 @@ window.location.hash = '#background=none';
// Run without data for speed (tests which need data can set it up themselves)
iD.fileFetcher.assetPath('../dist/');
var cached = iD.fileFetcher.cache();
const cached: any = iD.fileFetcher.cache();
// Initializing `coreContext` will try loading the locale data and English locale strings:
cached.locales = { en: { rtl: false, pct: 1 } };
@@ -93,24 +131,10 @@ cached.deprecated = [];
// Initializing `coreContext` initializes `_uploader`, which tries loading:
cached.discarded = {};
window.d3 = iD.d3; // Remove this if we can avoid exporting all of d3.js
mocha.setup({
timeout: 5000, // 5 sec
ui: 'bdd',
globals: [
'__onmousemove.zoom',
'__onmouseup.zoom',
'__onkeydown.select',
'__onkeyup.select',
'__onclick.draw',
'__onclick.draw-block'
]
});
expect = chai.expect;
window.d3 = iD.d3; // Remove this if we can avoid exporting all of d3.js
delete window.PointerEvent; // force the browser to use mouse events
// @ts-expect-error
delete window.PointerEvent; // force the browser to use mouse events
// some sticky fallbacks
const capabilities = `<?xml version="1.0" encoding="UTF-8"?>

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"noEmit": true,
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"types": ["vitest/globals"]
},
"include": ["modules", "test"]
}

11
vitest.config.mts Normal file
View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
css: true,
environment: 'jsdom',
globals: true,
include: ['test/spec/**/*'],
setupFiles: ['./test/spec_helpers.mts'],
},
});