diff --git a/package-lock.json b/package-lock.json index 5457f9851..1671b7b3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "@types/d3": "^7.4.3", "@types/happen": "^0.3.0", "@types/lodash-es": "^4.17.12", + "@types/node": "^22.13.4", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^4.0.0", "autoprefixer": "^10.4.20", @@ -1958,6 +1959,15 @@ "@types/lodash": "*" } }, + "node_modules/@types/node": { + "version": "22.13.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", + "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "devOptional": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/@types/pako": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", @@ -23639,6 +23649,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "devOptional": true + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/package.json b/package.json index 064ab4b2a..f7c6e2659 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/d3": "^7.4.3", "@types/happen": "^0.3.0", "@types/lodash-es": "^4.17.12", + "@types/node": "^22.13.4", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^4.0.0", "autoprefixer": "^10.4.20", diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 226a11161..2f0456aec 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.behaviorHash', function () { var hash, context; @@ -30,16 +32,14 @@ describe('iD.behaviorHash', function () { expect(context.map().zoom()).to.equal(20.0); }); - it('centerZooms map at requested coordinates on hash change', function (done) { + it('centerZooms map at requested coordinates on hash change', async () => { hash(); - d3.select(window).on('hashchange', function () { - expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); - expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); - expect(context.map().zoom()).to.equal(20.0); - d3.select(window).on('hashchange', null); - done(); - }); window.location.hash = '#background=none&map=20.00/38.87952/-77.02405'; + await new Promise(cb => { d3.select(window).on('hashchange', cb); }); + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); + d3.select(window).on('hashchange', null); }); it('sets hadLocation if map-location is in local storage', function () { @@ -68,16 +68,14 @@ describe('iD.behaviorHash', function () { iD.prefs('map-location', null); }); - it('stores the current zoom and coordinates in window.location.hash on map move events', function (done) { + it('stores the current zoom and coordinates in window.location.hash on map move events', async () => { hash(); context.map().center([-77.0, 38.9]); context.map().zoom(2.0); - window.setTimeout(function() { - // the hash might contain other things like `disable_features` - expect(window.location.hash).to.include('background=none'); - expect(window.location.hash).to.include('map=2.00/38.9/-77.0'); - done(); - }, 600); + await setTimeout(600); + // the hash might contain other things like `disable_features` + expect(window.location.hash).to.include('background=none'); + expect(window.location.hash).to.include('map=2.00/38.9/-77.0'); }); it('accepts default changeset comment as hash parameter', function () { diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index 606d25229..d082e7dfb 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.behaviorSelect', function() { var a, b, context, behavior, container; @@ -55,62 +57,50 @@ describe('iD.behaviorSelect', function() { expect(context.mode().id).to.eql('browse'); }); - it('click on entity selects the entity', function(done) { + it('click on entity selects the entity', async () => { var el = context.surface().selectAll('.' + a.id).node(); simulateClick(el, {}); - window.setTimeout(function() { - expect(context.selectedIDs()).to.eql([a.id]); - done(); - }, 50); + await setTimeout(50); + expect(context.selectedIDs()).to.eql([a.id]); }); - it('click on empty space clears the selection', function(done) { + it('click on empty space clears the selection', async () => { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().node(); simulateClick(el, {}); - window.setTimeout(function() { - expect(context.mode().id).to.eql('browse'); - done(); - }, 50); + await setTimeout(50); + expect(context.mode().id).to.eql('browse'); }); - it('shift-click on unselected entity adds it to the selection', function(done) { + it('shift-click on unselected entity adds it to the selection', async () => { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().selectAll('.' + b.id).node(); simulateClick(el, { shiftKey: true }); - window.setTimeout(function() { - expect(context.selectedIDs()).to.eql([a.id, b.id]); - done(); - }, 50); + await setTimeout(50); + expect(context.selectedIDs()).to.eql([a.id, b.id]); }); - it('shift-click on selected entity removes it from the selection', function(done) { + it('shift-click on selected entity removes it from the selection', async () => { context.enter(iD.modeSelect(context, [a.id, b.id])); var el = context.surface().selectAll('.' + b.id).node(); simulateClick(el, { shiftKey: true }); - window.setTimeout(function() { - expect(context.selectedIDs()).to.eql([a.id]); - done(); - }, 50); + await setTimeout(50); + expect(context.selectedIDs()).to.eql([a.id]); }); - it('shift-click on last selected entity clears the selection', function(done) { + it('shift-click on last selected entity clears the selection', async () => { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().selectAll('.' + a.id).node(); simulateClick(el, { shiftKey: true }); - window.setTimeout(function() { - expect(context.mode().id).to.eql('browse'); - done(); - }, 50); + await setTimeout(50); + expect(context.mode().id).to.eql('browse'); }); - it('shift-click on empty space leaves the selection unchanged', function(done) { + it('shift-click on empty space leaves the selection unchanged', async () => { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().node(); simulateClick(el, { shiftKey: true }); - window.setTimeout(function() { - expect(context.selectedIDs()).to.eql([a.id]); - done(); - }, 50); + await setTimeout(50); + expect(context.selectedIDs()).to.eql([a.id]); }); }); diff --git a/test/spec/core/LocationManager.js b/test/spec/core/LocationManager.js index 955ba8169..6c29f834a 100644 --- a/test/spec/core/LocationManager.js +++ b/test/spec/core/LocationManager.js @@ -38,56 +38,35 @@ describe('LocationManager', () => { describe('#mergeLocationSets', () => { - it('returns a promise rejected if not passed an array', done => { + it('returns a promise rejected if not passed an array', async () => { const prom = locationManager.mergeLocationSets({}); - prom - .then(() => { - done(new Error('This was supposed to fail, but somehow succeeded.')); - }) - .catch(err => { - expect(/^nothing to do/.test(err)).to.be.true; - done(); - }); - - window.setTimeout(() => {}, 20); // async - to let the promise settle in phantomjs + await expect(prom).rejects.toThrow(/^nothing to do/); }); - it('resolves locationSets, assigning locationSetID', done => { + it('resolves locationSets, assigning locationSetID', async () => { const data = [ { id: 'world', locationSet: { include: ['001'] } }, { id: 'usa', locationSet: { include: ['usa'] } } ]; const prom = locationManager.mergeLocationSets(data); - prom - .then(data => { - expect(data).to.be.a('array'); - expect(data[0].locationSetID).to.eql('+[Q2]'); - expect(data[1].locationSetID).to.eql('+[Q30]'); - done(); - }) - .catch(err => done(err)); - - window.setTimeout(() => {}, 20); // async - to let the promise settle in phantomjs + await prom; + expect(data).to.be.a('array'); + expect(data[0].locationSetID).to.eql('+[Q2]'); + expect(data[1].locationSetID).to.eql('+[Q30]'); }); - it('resolves locationSets, falls back to world locationSetID on errror', done => { + it('resolves locationSets, falls back to world locationSetID on errror', async () => { const data = [ { id: 'bogus1', locationSet: { foo: 'bar' } }, { id: 'bogus2', locationSet: { include: ['fake.geojson'] } } ]; const prom = locationManager.mergeLocationSets(data); - prom - .then(data => { - expect(data).to.be.a('array'); - expect(data[0].locationSetID).to.eql('+[Q2]'); - expect(data[1].locationSetID).to.eql('+[Q2]'); - done(); - }) - .catch(err => done(err)); - - window.setTimeout(() => {}, 20); // async - to let the promise settle in phantomjs + await prom; + expect(data).to.be.a('array'); + expect(data[0].locationSetID).to.eql('+[Q2]'); + expect(data[1].locationSetID).to.eql('+[Q2]'); }); }); @@ -127,26 +106,20 @@ describe('LocationManager', () => { expect(result3).to.be.an('object').that.has.all.keys('+[Q2]'); }); - it('returns valid locationSets at a given lon,lat', done => { + it('returns valid locationSets at a given lon,lat', async () => { // setup, load colorado.geojson and resolve some locationSets locationManager.mergeCustomGeoJSON(fc); - locationManager.mergeLocationSets([ + await locationManager.mergeLocationSets([ { id: 'OSM-World', locationSet: { include: ['001'] } }, { id: 'OSM-USA', locationSet: { include: ['us'] } }, { id: 'OSM-Colorado', locationSet: { include: ['colorado.geojson'] } } - ]) - .then(() => { - const result1 = locationManager.locationSetsAt([-108.557, 39.065]); // Grand Junction - expect(result1).to.be.an('object').that.has.all.keys('+[Q2]', '+[Q30]', '+[colorado.geojson]'); - const result2 = locationManager.locationSetsAt([-74.481, 40.797]); // Morristown - expect(result2).to.be.an('object').that.has.all.keys('+[Q2]', '+[Q30]'); - const result3 = locationManager.locationSetsAt([13.575, 41.207,]); // Gaeta - expect(result3).to.be.an('object').that.has.all.keys('+[Q2]'); - done(); - }) - .catch(err => done(err)); - - window.setTimeout(() => {}, 20); // async - to let the promise settle in phantomjs + ]); + const result1 = locationManager.locationSetsAt([-108.557, 39.065]); // Grand Junction + expect(result1).to.be.an('object').that.has.all.keys('+[Q2]', '+[Q30]', '+[colorado.geojson]'); + const result2 = locationManager.locationSetsAt([-74.481, 40.797]); // Morristown + expect(result2).to.be.an('object').that.has.all.keys('+[Q2]', '+[Q30]'); + const result3 = locationManager.locationSetsAt([13.575, 41.207,]); // Gaeta + expect(result3).to.be.an('object').that.has.all.keys('+[Q2]'); }); }); diff --git a/test/spec/core/file_fetcher.js b/test/spec/core/file_fetcher.js index a209b5403..2b77b9cff 100644 --- a/test/spec/core/file_fetcher.js +++ b/test/spec/core/file_fetcher.js @@ -13,50 +13,31 @@ describe('iD.coreFileFetcher', function() { }); describe('#get', function() { - it('returns a promise resolved if we already have the data', function(done) { + it('returns a promise resolved if we already have the data', async () => { var data = iD.coreFileFetcher(); data.cache().test = { hello: 'world' }; var prom = data.get('test'); expect(prom).to.be.a('promise'); - prom - .then(function(data) { - expect(data).to.be.a('object'); - expect(data.hello).to.eql('world'); - done(); - }) - .catch(function(err) { - done(err); - }); + data = await prom; + expect(data).to.be.a('object'); + expect(data.hello).to.eql('world'); }); - it('returns a promise rejected if we can not get the data', function(done) { + it('returns a promise rejected if we can not get the data', async () => { var data = iD.coreFileFetcher().assetPath('../dist/'); var prom = data.get('wat'); - prom - .then(function(data) { - done(new Error('We were not supposed to get data but did: ' + data)); - }) - .catch(function(err) { - expect(/^Unknown data file/.test(err)).to.be.true; - done(); - }); + await expect(prom).rejects.toThrow(/^Unknown data file/); }); - it('returns a promise to fetch data if we do not already have the data', function(done) { + it('returns a promise to fetch data if we do not already have the data', async () => { var files = { 'intro_graph': 'data/intro_graph.min.json' }; var data = iD.coreFileFetcher().assetPath('../dist/').fileMap(files); var prom = data.get('intro_graph'); expect(prom).to.be.a('promise'); - prom - .then(function(data) { - expect(data).to.be.a('object'); - expect(data.n2061.tags.name).to.eql('Three Rivers City Hall'); - done(); - }) - .catch(function(err) { - done(err); - }); + data = await prom; + expect(data).to.be.a('object'); + expect(data.n2061.tags.name).to.eql('Three Rivers City Hall'); }); }); diff --git a/test/spec/core/history.js b/test/spec/core/history.js index d9313a454..4e8d73e4e 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.coreHistory', function () { var context, history, spy; var actionNoop = function(g) { return g; }; @@ -68,15 +70,13 @@ describe('iD.coreHistory', function () { expect(history.undoAnnotation()).to.equal('annotation'); }); - it('performs transitionable actions in a transition', function (done) { + it('performs transitionable actions in a transition', async () => { var action1 = function() { return iD.coreGraph(); }; action1.transitionable = true; history.on('change', spy); history.perform(action1); - window.setTimeout(function() { - expect(spy.callCount).to.be.above(2); - done(); - }, 300); + await setTimeout(300); + expect(spy.callCount).to.be.above(2); }); }); diff --git a/test/spec/core/validator.js b/test/spec/core/validator.js index ce10bd80b..b86261b09 100644 --- a/test/spec/core/validator.js +++ b/test/spec/core/validator.js @@ -24,7 +24,7 @@ describe('iD.coreValidator', function() { expect(issues).to.have.lengthOf(0); }); - it('validate returns a promise, fulfilled when the validation has completed', function(done) { + it('validate returns a promise, fulfilled when the validation has completed', async () => { createInvalidWay(); var validator = new iD.coreValidator(context); validator.init(); @@ -32,22 +32,16 @@ describe('iD.coreValidator', function() { expect(issues).to.have.lengthOf(0); var prom = validator.validate(); - prom - .then(function() { - issues = validator.getIssues(); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('missing_tag'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }) - .catch(function(err) { - done(err); - }); + await prom; + issues = validator.getIssues(); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('missing_tag'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('removes validation issue when highway is no longer disconnected', function(done) { + it('removes validation issue when highway is no longer disconnected', async () => { // Add a way which is disconnected from the rest of the map var n1 = iD.osmNode({ id: 'n-1', loc: [4, 4] }); var n2 = iD.osmNode({ id: 'n-2', loc: [4, 5] }); @@ -59,32 +53,25 @@ describe('iD.coreValidator', function() { ); var validator = new iD.coreValidator(context); validator.init(); - validator.validate().then(function() { - // Should produce disconnected way error - let issues = validator.getIssues(); - expect(issues).to.have.lengthOf(1); + await validator.validate(); + // Should produce disconnected way error + let issues = validator.getIssues(); + expect(issues).to.have.lengthOf(1); - // Add new node with entrance node to simulate connection with rest of map - var n3 = iD.osmNode({ id: 'n-3', loc: [4, 6], tags: { 'entrance': 'yes' } }); - var w2 = iD.osmWay({ id: 'w-2', nodes: ['n-2', 'n-3'], tags: { 'highway': 'unclassified' } }); - context.perform( - iD.actionAddEntity(n3), - iD.actionAddEntity(w2) - ); - validator.validate().then(function() { - // Should be no errors - issues = validator.getIssues(); - expect(issues).to.have.lengthOf(0); - done(); - }).catch(function(err) { - done(err); - }); - }).catch(function(err) { - done(err); - }); + // Add new node with entrance node to simulate connection with rest of map + var n3 = iD.osmNode({ id: 'n-3', loc: [4, 6], tags: { 'entrance': 'yes' } }); + var w2 = iD.osmWay({ id: 'w-2', nodes: ['n-2', 'n-3'], tags: { 'highway': 'unclassified' } }); + context.perform( + iD.actionAddEntity(n3), + iD.actionAddEntity(w2) + ); + await validator.validate(); + // Should be no errors + issues = validator.getIssues(); + expect(issues).to.have.lengthOf(0); }); - it('add validation issue when highway becomes disconnected', function(done) { + it('add validation issue when highway becomes disconnected', async () => { // Add a way which is connected to another way with an entrance node to simulate connection with rest of map var n1 = iD.osmNode({ id: 'n-1', loc: [4, 4] }); var n2 = iD.osmNode({ id: 'n-2', loc: [4, 5] }); @@ -100,27 +87,19 @@ describe('iD.coreValidator', function() { ); var validator = new iD.coreValidator(context); validator.init(); - validator.validate().then(function() { - // Should be no errors - let issues = validator.getIssues(); - expect(issues).to.have.lengthOf(0); + await validator.validate(); + // Should be no errors + let issues = validator.getIssues(); + expect(issues).to.have.lengthOf(0); - // delete second way -> first way becomes disconnected form the rest of the network - context.perform( - iD.actionDeleteWay(w2.id) - ); + // delete second way -> first way becomes disconnected form the rest of the network + context.perform( + iD.actionDeleteWay(w2.id) + ); - validator.validate().then(function() { - // Should produce disconnected way error - issues = validator.getIssues(); - expect(issues).to.have.lengthOf(1); - done(); - }).catch(function(err) { - done(err); - }); - }).catch(function(err) { - done(err); - }); + await validator.validate(); + // Should produce disconnected way error + issues = validator.getIssues(); + expect(issues).to.have.lengthOf(1); }); - }); diff --git a/test/spec/presets/index.js b/test/spec/presets/index.js index 9c3c39efe..0214c2fa2 100644 --- a/test/spec/presets/index.js +++ b/test/spec/presets/index.js @@ -48,55 +48,47 @@ describe('iD.presetIndex', function () { park: { tags: { leisure: 'park' }, geometry: ['point', 'area'] } }; - it('returns a collection containing presets matching a geometry and tags', function (done) { + it('returns a collection containing presets matching a geometry and tags', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - var way = iD.osmWay({ tags: { highway: 'residential' } }); - var graph = iD.coreGraph([way]); - expect(presets.match(way, graph).id).to.eql('residential'); - done(); - }); + await presets.ensureLoaded(); + var way = iD.osmWay({ tags: { highway: 'residential' } }); + var graph = iD.coreGraph([way]); + expect(presets.match(way, graph).id).to.eql('residential'); }); - it('returns the appropriate fallback preset when no tags match', function (done) { + it('returns the appropriate fallback preset when no tags match', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var point = iD.osmNode(); var line = iD.osmWay({ tags: { foo: 'bar' } }); var graph = iD.coreGraph([point, line]); - presets.ensureLoaded().then(function() { - expect(presets.match(point, graph).id).to.eql('point'); - expect(presets.match(line, graph).id).to.eql('line'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.match(point, graph).id).to.eql('point'); + expect(presets.match(line, graph).id).to.eql('line'); }); - it('matches vertices on a line as points', function (done) { + it('matches vertices on a line as points', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var point = iD.osmNode({ tags: { leisure: 'park' } }); var line = iD.osmWay({ nodes: [point.id], tags: { 'highway': 'residential' } }); var graph = iD.coreGraph([point, line]); - presets.ensureLoaded().then(function() { - expect(presets.match(point, graph).id).to.eql('point'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.match(point, graph).id).to.eql('point'); }); - it('matches vertices on an addr:interpolation line as points', function (done) { + it('matches vertices on an addr:interpolation line as points', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var point = iD.osmNode({ tags: { leisure: 'park' } }); var line = iD.osmWay({ nodes: [point.id], tags: { 'addr:interpolation': 'even' } }); var graph = iD.coreGraph([point, line]); - presets.ensureLoaded().then(function() { - expect(presets.match(point, graph).id).to.eql('park'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.match(point, graph).id).to.eql('park'); }); }); @@ -112,68 +104,55 @@ describe('iD.presetIndex', function () { 'natural/wood': { tags: { 'natural': 'wood' }, geometry: ['point', 'area'] } }; - it('includes keys for presets with area geometry', function (done) { + it('includes keys for presets with area geometry', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys()).to.include.keys('natural'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys()).to.include.keys('natural'); }); - it('discards key-values for presets with a line geometry', function (done) { + it('discards key-values for presets with a line geometry', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys().natural).to.include.keys('tree_row'); - expect(presets.areaKeys().natural.tree_row).to.be.true; - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys().natural).to.include.keys('tree_row'); + expect(presets.areaKeys().natural.tree_row).to.be.true; + }); - it('discards key-values for presets with both area and line geometry', function (done) { + it('discards key-values for presets with both area and line geometry', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys().leisure).to.include.keys('track'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys().leisure).to.include.keys('track'); }); - it('does not discard key-values for presets with neither area nor line geometry', function (done) { + it('does not discard key-values for presets with neither area nor line geometry', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys().natural).not.to.include.keys('peak'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys().natural).not.to.include.keys('peak'); }); - it('does not discard generic \'*\' key-values', function (done) { + it('does not discard generic \'*\' key-values', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys().natural).not.to.include.keys('natural'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys().natural).not.to.include.keys('natural'); }); - it('ignores keys like \'highway\' that are assumed to be lines', function (done) { + it('ignores keys like \'highway\' that are assumed to be lines', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys()).not.to.include.keys('highway'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys()).not.to.include.keys('highway'); }); - it('ignores suggestion presets', function (done) { + it('ignores suggestion presets', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.areaKeys()).not.to.include.keys('amenity'); - done(); - }); + await presets.ensureLoaded(); + expect(presets.areaKeys()).not.to.include.keys('amenity'); }); }); @@ -185,94 +164,83 @@ describe('iD.presetIndex', function () { bench: { tags: { amenity: 'bench' }, geometry: ['point', 'line'] } }; - it('addablePresetIDs is initially null', function (done) { + it('addablePresetIDs is initially null', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - expect(presets.addablePresetIDs()).to.be.null; - done(); - }); + await presets.ensureLoaded(); + expect(presets.addablePresetIDs()).to.be.null; }); - it('can set and get addablePresetIDs', function (done) { + it('can set and get addablePresetIDs', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { + await presets.ensureLoaded(); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.true; + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.true; - var ids = new Set(['residential']); // can only add preset with this ID - presets.addablePresetIDs(ids); + var ids = new Set(['residential']); // can only add preset with this ID + presets.addablePresetIDs(ids); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.false; - expect(presets.addablePresetIDs()).to.eql(ids); + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.false; + expect(presets.addablePresetIDs()).to.eql(ids); - presets.addablePresetIDs(null); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.true; - - done(); - }); + presets.addablePresetIDs(null); + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.true; }); - it('ignores invalid IDs in addablePresetIDs', function (done) { + it('ignores invalid IDs in addablePresetIDs', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { + await presets.ensureLoaded(); - expect(presets.item(null)).to.eql(undefined); - expect(presets.item(undefined)).to.eql(undefined); - expect(presets.item('')).to.eql(undefined); - expect(presets.item('garbage')).to.eql(undefined); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.true; + expect(presets.item(null)).to.eql(undefined); + expect(presets.item(undefined)).to.eql(undefined); + expect(presets.item('')).to.eql(undefined); + expect(presets.item('garbage')).to.eql(undefined); + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.true; - var ids = new Set([null, undefined, '', 'garbage', 'residential']); // can only add preset with these IDs - presets.addablePresetIDs(ids); + var ids = new Set([null, undefined, '', 'garbage', 'residential']); // can only add preset with these IDs + presets.addablePresetIDs(ids); - expect(presets.item(null)).to.eql(undefined); - expect(presets.item(undefined)).to.eql(undefined); - expect(presets.item('')).to.eql(undefined); - expect(presets.item('garbage')).to.eql(undefined); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.false; - expect(presets.addablePresetIDs()).to.eql(ids); + expect(presets.item(null)).to.eql(undefined); + expect(presets.item(undefined)).to.eql(undefined); + expect(presets.item('')).to.eql(undefined); + expect(presets.item('garbage')).to.eql(undefined); + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.false; + expect(presets.addablePresetIDs()).to.eql(ids); - presets.addablePresetIDs(null); - expect(presets.item(null)).to.eql(undefined); - expect(presets.item(undefined)).to.eql(undefined); - expect(presets.item('')).to.eql(undefined); - expect(presets.item('garbage')).to.eql(undefined); - expect(presets.item('residential').addable()).to.be.true; - expect(presets.item('park').addable()).to.be.true; - - done(); - }); + presets.addablePresetIDs(null); + expect(presets.item(null)).to.eql(undefined); + expect(presets.item(undefined)).to.eql(undefined); + expect(presets.item('')).to.eql(undefined); + expect(presets.item('garbage')).to.eql(undefined); + expect(presets.item('residential').addable()).to.be.true; + expect(presets.item('park').addable()).to.be.true; }); - it('addablePresetIDs are default presets', function (done) { + it('addablePresetIDs are default presets', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); - presets.ensureLoaded().then(function() { - var ids = new Set(['bench', 'residential']); // can only add presets with these IDs - presets.addablePresetIDs(ids); + await presets.ensureLoaded(); + var ids = new Set(['bench', 'residential']); // can only add presets with these IDs + presets.addablePresetIDs(ids); - var areaDefaults = presets.defaults('area', 10).collection; - expect(areaDefaults.length).to.eql(0); + var areaDefaults = presets.defaults('area', 10).collection; + expect(areaDefaults.length).to.eql(0); - var pointDefaults = presets.defaults('point', 10).collection; - expect(pointDefaults.length).to.eql(1); - expect(pointDefaults[0].id).to.eql('bench'); + var pointDefaults = presets.defaults('point', 10).collection; + expect(pointDefaults.length).to.eql(1); + expect(pointDefaults[0].id).to.eql('bench'); - var lineDefaults = presets.defaults('line', 10).collection; - expect(lineDefaults.length).to.eql(2); - expect(lineDefaults[0].id).to.eql('bench'); - expect(lineDefaults[1].id).to.eql('residential'); - - done(); - }); + var lineDefaults = presets.defaults('line', 10).collection; + expect(lineDefaults.length).to.eql(2); + expect(lineDefaults[0].id).to.eql('bench'); + expect(lineDefaults[1].id).to.eql('residential'); }); }); @@ -347,40 +315,34 @@ describe('iD.presetIndex', function () { } }; - it('prefers building to multipolygon', function (done) { + it('prefers building to multipolygon', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var relation = iD.osmRelation({ tags: { type: 'multipolygon', building: 'yes' } }); var graph = iD.coreGraph([relation]); - presets.ensureLoaded().then(function() { - var match = presets.match(relation, graph); - expect(match.id).to.eql('building'); - done(); - }); + await presets.ensureLoaded(); + var match = presets.match(relation, graph); + expect(match.id).to.eql('building'); }); - it('prefers building to address', function (done) { + it('prefers building to address', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var way = iD.osmWay({ tags: { area: 'yes', building: 'yes', 'addr:housenumber': '1234' } }); var graph = iD.coreGraph([way]); - presets.ensureLoaded().then(function() { - var match = presets.match(way, graph); - expect(match.id).to.eql('building'); - done(); - }); + await presets.ensureLoaded(); + var match = presets.match(way, graph); + expect(match.id).to.eql('building'); }); - it('prefers pedestrian to area', function (done) { + it('prefers pedestrian to area', async () => { iD.fileFetcher.cache().preset_presets = testPresets; var presets = iD.presetIndex(); var way = iD.osmWay({ tags: { area: 'yes', highway: 'pedestrian' } }); var graph = iD.coreGraph([way]); - presets.ensureLoaded().then(function() { - var match = presets.match(way, graph); - expect(match.id).to.eql('highway/pedestrian_area'); - done(); - }); + await presets.ensureLoaded(); + var match = presets.match(way, graph); + expect(match.id).to.eql('highway/pedestrian_area'); }); }); diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js index 4aa234a1c..09af253e8 100644 --- a/test/spec/renderer/map.js +++ b/test/spec/renderer/map.js @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import css from '../../../css/55_cursors.css?raw'; describe('iD.Map', function() { @@ -45,26 +46,22 @@ describe('iD.Map', function() { }); describe('#zoomIn', function() { - it('increments zoom', function(done) { + it('increments zoom', async () => { expect(map.zoom(4)).to.equal(map); map.zoomIn(); - window.setTimeout(function() { - d3.timerFlush(); - expect(map.zoom()).to.be.closeTo(5, 1e-6); - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(map.zoom()).to.be.closeTo(5, 1e-6); }); }); describe('#zoomOut', function() { - it('decrements zoom', function(done) { + it('decrements zoom', async () => { expect(map.zoom(4)).to.equal(map); map.zoomOut(); - window.setTimeout(function() { - d3.timerFlush(); - expect(map.zoom()).to.be.closeTo(3, 1e-6); - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(map.zoom()).to.be.closeTo(3, 1e-6); }); }); @@ -102,15 +99,13 @@ describe('iD.Map', function() { }); describe('#centerEase', function() { - it('sets center', function(done) { + it('sets center', async () => { expect(map.center([10, 10])).to.equal(map); expect(map.centerEase([20, 20], 250)).to.equal(map); - window.setTimeout(function() { - d3.timerFlush(); - expect(map.center()[0]).to.be.closeTo(20, 1e-6); - expect(map.center()[1]).to.be.closeTo(20, 1e-6); - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(map.center()[0]).to.be.closeTo(20, 1e-6); + expect(map.center()[1]).to.be.closeTo(20, 1e-6); }); }); diff --git a/test/spec/services/kartaview.js b/test/spec/services/kartaview.js index c0eb5ce41..346914fbf 100644 --- a/test/spec/services/kartaview.js +++ b/test/spec/services/kartaview.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceKartaview', function() { var dimensions = [64, 64]; var context, kartaview; @@ -52,7 +54,7 @@ describe('iD.serviceKartaview', function() { }); describe('#loadImages', function() { - it('fires loadedImages when images are loaded', function(done) { + it('fires loadedImages when images are loaded', async () => { var data = { status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' }, currentPageItems:[{ @@ -101,15 +103,13 @@ describe('iD.serviceKartaview', function() { headers: { 'Content-Type': 'application/json' } }); - kartaview.on('loadedImages', function() { - expect(fetchMock.calls().length).to.eql(1); // 1 nearby-photos - done(); - }); - kartaview.loadImages(context.projection); + + await new Promise(cb => { kartaview.on('loadedImages', cb); }); + expect(fetchMock.calls().length).to.eql(1); // 1 nearby-photos }); - it('does not load images around null island', function (done) { + it('does not load images around null island', async () => { var data = { status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' }, currentPageItems:[{ @@ -164,14 +164,12 @@ describe('iD.serviceKartaview', function() { kartaview.on('loadedImages', spy); kartaview.loadImages(context.projection); - window.setTimeout(function() { - expect(spy).to.have.been.not.called; - expect(fetchMock.calls().length).to.eql(0); // no tile requests of any kind - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.not.called; + expect(fetchMock.calls().length).to.eql(0); // no tile requests of any kind }); - it('loads multiple pages of image results', function(done) { + it('loads multiple pages of image results', async () => { var features = []; for (var i = 0; i < 1000; i++) { var key = String(i); @@ -202,12 +200,10 @@ describe('iD.serviceKartaview', function() { headers: { 'Content-Type': 'application/json' } }); - kartaview.on('loadedImages', function() { - expect(fetchMock.calls().length).to.eql(2); // 2 nearby-photos - done(); - }); - kartaview.loadImages(context.projection); + + await new Promise(cb => { kartaview.on('loadedImages', cb); }); + expect(fetchMock.calls().length).to.eql(2); // 2 nearby-photos }); }); diff --git a/test/spec/services/nominatim.js b/test/spec/services/nominatim.js index 47a552180..3dbf5f7d1 100644 --- a/test/spec/services/nominatim.js +++ b/test/spec/services/nominatim.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceNominatim', function() { var nominatim; @@ -25,7 +27,7 @@ describe('iD.serviceNominatim', function() { } describe('#countryCode', function() { - it('calls the given callback with the results of the country code query', function(done) { + it('calls the given callback with the results of the country code query', async () => { var callback = sinon.spy(); fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { body: '{"address":{"country_code":"at"}}', @@ -35,18 +37,16 @@ describe('iD.serviceNominatim', function() { nominatim.countryCode([16, 48], callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} - ); - expect(callback).to.have.been.calledWithExactly(null, 'at'); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} + ); + expect(callback).to.have.been.calledWithExactly(null, 'at'); }); }); describe('#reverse', function() { - it('should not cache distant result', function(done) { + it('should not cache distant result', async () => { var callback = sinon.spy(); fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { body: '{"address":{"country_code":"at"}}', @@ -56,36 +56,33 @@ describe('iD.serviceNominatim', function() { nominatim.reverse([16, 48], callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} - ); - expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} + ); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); - fetchMock.reset(); - fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { - body: '{"address":{"country_code":"cz"}}', - status: 200, - headers: { 'Content-Type': 'application/json' } - }); + fetchMock.reset(); + fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { + body: '{"address":{"country_code":"cz"}}', + status: 200, + headers: { 'Content-Type': 'application/json' } + }); - callback = sinon.spy(); - nominatim.reverse([17, 49], callback); + callback = sinon.spy(); + nominatim.reverse([17, 49], callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {zoom: '13', format: 'json', addressdetails: '1', lat: '49', lon: '17'} - ); - expect(fetchMock.calls()[0][1].headers).to.eql({ - 'Accept-Language': 'en' - }); - expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'cz'}}); - done(); - }, 50); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {zoom: '13', format: 'json', addressdetails: '1', lat: '49', lon: '17'} + ); + expect(fetchMock.calls()[0][1].headers).to.eql({ + 'Accept-Language': 'en' + }); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'cz'}}); }); - it('should cache nearby result', function(done) { + it('should cache nearby result', async () => { var callback = sinon.spy(); fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { body: '{"address":{"country_code":"at"}}', @@ -95,25 +92,22 @@ describe('iD.serviceNominatim', function() { nominatim.reverse([16, 48], callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} - ); - expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'} + ); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); - fetchMock.resetHistory(); + fetchMock.resetHistory(); - callback = sinon.spy(); - nominatim.reverse([16.000001, 48.000001], callback); + callback = sinon.spy(); + nominatim.reverse([16.000001, 48.000001], callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); - done(); - }, 50); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); }); - it('calls the given callback with an error', function(done) { + it('calls the given callback with an error', async () => { var callback = sinon.spy(); fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/reverse'), { body: '{"error":"Unable to geocode"}', @@ -124,19 +118,17 @@ describe('iD.serviceNominatim', function() { nominatim.reverse([1000, 1000], callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {zoom: '13', format: 'json', addressdetails: '1', lat: '1000', lon: '1000'} - ); - expect(callback).to.have.been.calledWithExactly('Unable to geocode'); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {zoom: '13', format: 'json', addressdetails: '1', lat: '1000', lon: '1000'} + ); + expect(callback).to.have.been.calledWithExactly('Unable to geocode'); }); }); describe('#search', function() { - it('calls the given callback with the results of the search query', function(done) { + it('calls the given callback with the results of the search query', async () => { var callback = sinon.spy(); fetchMock.mock(new RegExp('https://nominatim.openstreetmap.org/search'), { body: '[{"place_id":"158484588","osm_type":"relation","osm_id":"188022","boundingbox":["39.867005","40.1379593","-75.2802976","-74.9558313"],"lat":"39.9523993","lon":"-75.1635898","display_name":"Philadelphia, Philadelphia County, Pennsylvania, United States of America","class":"place","type":"city","importance":0.83238050437778}]', @@ -146,18 +138,16 @@ describe('iD.serviceNominatim', function() { nominatim.search('philadelphia', callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql({ - q: 'philadelphia', - format: 'json', - limit: '10' - }); - expect(fetchMock.calls()[0][1].headers).to.eql({ - 'Accept-Language': 'en' - }); - expect(callback).to.have.been.calledOnce; - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql({ + q: 'philadelphia', + format: 'json', + limit: '10' + }); + expect(fetchMock.calls()[0][1].headers).to.eql({ + 'Accept-Language': 'en' + }); + expect(callback).to.have.been.calledOnce; }); }); diff --git a/test/spec/services/osm.js b/test/spec/services/osm.js index fcc9d2227..260d7e7fb 100644 --- a/test/spec/services/osm.js +++ b/test/spec/services/osm.js @@ -1,3 +1,6 @@ +import { setTimeout } from 'node:timers/promises'; +import { promisify } from 'node:util'; + describe('iD.serviceOsm', function () { var context, connection, spy; var serverXHR; @@ -149,21 +152,18 @@ describe('iD.serviceOsm', function () { ' ]' + '}'; - it('returns an object', function(done) { + it('returns an object', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: response, status: 200, headers: { 'Content-Type': 'application/json' } }); - connection.loadFromAPI(path, function (err, payload) { - expect(err).to.not.be.ok; - expect(typeof payload).to.eql('object'); - done(); - }); + const payload = await promisify(connection.loadFromAPI).call(connection, path); + expect(typeof payload).to.eql('object'); }); - it('retries an authenticated call unauthenticated if 401 Unauthorized', function (done) { + it('retries an authenticated call unauthenticated if 401 Unauthorized', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: response, status: 200, @@ -174,19 +174,15 @@ describe('iD.serviceOsm', function () { login(); - connection.loadFromAPI(path, function (err, xml) { - expect(err).to.be.not.ok; - expect(typeof xml).to.eql('object'); - expect(connection.authenticated()).to.be.not.ok; - expect(fetchMock.called()).to.be.true; - - done(); - }); - + const xml = promisify(connection.loadFromAPI).call(connection, path); serverXHR.respond(); + + expect(typeof await xml).to.eql('object'); + expect(connection.authenticated()).to.be.not.ok; + expect(fetchMock.called()).to.be.true; }); - it('retries an authenticated call unauthenticated if 401 Unauthorized', function (done) { + it('retries an authenticated call unauthenticated if 401 Unauthorized', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: response, status: 200, @@ -196,19 +192,16 @@ describe('iD.serviceOsm', function () { [401, { 'Content-Type': 'text/plain' }, 'Unauthorized']); login(); - connection.loadFromAPI(path, function (err, xml) { - expect(err).to.be.not.ok; - expect(typeof xml).to.eql('object'); - expect(connection.authenticated()).to.be.not.ok; - expect(fetchMock.called()).to.be.true; - - done(); - }); + const xml = promisify(connection.loadFromAPI).call(connection, path); serverXHR.respond(); + + expect(typeof await xml).to.eql('object'); + expect(connection.authenticated()).to.be.not.ok; + expect(fetchMock.called()).to.be.true; }); - it('retries an authenticated call unauthenticated if 403 Forbidden', function (done) { + it('retries an authenticated call unauthenticated if 403 Forbidden', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: response, status: 200, @@ -218,20 +211,16 @@ describe('iD.serviceOsm', function () { [403, { 'Content-Type': 'text/plain' }, 'Forbidden']); login(); - connection.loadFromAPI(path, function (err, xml) { - expect(err).to.be.not.ok; - expect(typeof xml).to.eql('object'); - expect(connection.authenticated()).to.be.not.ok; - expect(fetchMock.called()).to.be.true; - - done(); - }); - + const xml = promisify(connection.loadFromAPI).call(connection, path); serverXHR.respond(); + + expect(typeof await xml).to.eql('object'); + expect(connection.authenticated()).to.be.not.ok; + expect(fetchMock.called()).to.be.true; }); - it('dispatches change event if 509 Bandwidth Limit Exceeded', function (done) { + it('dispatches change event if 509 Bandwidth Limit Exceeded', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: 'Bandwidth Limit Exceeded', status: 509, @@ -240,14 +229,14 @@ describe('iD.serviceOsm', function () { logout(); connection.on('change', spy); - connection.loadFromAPI(path, function (err) { - expect(err).to.have.property('status', 509); - expect(spy).to.have.been.calledOnce; - done(); - }); + const promise = promisify(connection.loadFromAPI).call(connection, path); + + await expect(promise).rejects.toThrow(expect.objectContaining({ status: 509 })); + + expect(spy).to.have.been.calledOnce; }); - it('dispatches change event if 429 Too Many Requests', function (done) { + it('dispatches change event if 429 Too Many Requests', async () => { fetchMock.mock('https://www.openstreetmap.org' + path, { body: '429 Too Many Requests', status: 429, @@ -256,14 +245,13 @@ describe('iD.serviceOsm', function () { logout(); connection.on('change', spy); - connection.loadFromAPI(path, function (err) { - expect(err).to.have.property('status', 429); - expect(spy).to.have.been.calledOnce; - done(); - }); + const promise = promisify(connection.loadFromAPI).call(connection, path); + + await expect(promise).rejects.toThrow(expect.objectContaining({ status: 429 })); + expect(spy).to.have.been.calledOnce; }); - it('uses apiUrl', function(done) { + it('uses apiUrl', async () => { fetchMock.mock('https://api.openstreetmap.org' + path, { body: response, status: 200, @@ -275,12 +263,10 @@ describe('iD.serviceOsm', function () { apiUrl: 'https://api.openstreetmap.org' }); - connection.loadFromAPI(path, function (err) { - expect(err).to.not.be.ok; - expect(fetchMock.calls().length).to.eql(1); - expect(fetchMock.calls()[0][0]).to.eql('https://api.openstreetmap.org' + path); - done(); - }); + await promisify(connection.loadFromAPI).call(connection, path); + + expect(fetchMock.calls().length).to.eql(1); + expect(fetchMock.calls()[0][0]).to.eql('https://api.openstreetmap.org' + path); }); }); @@ -303,7 +289,7 @@ describe('iD.serviceOsm', function () { .clipExtent([[0,0], dimensions]); }); - it('calls callback when data tiles are loaded', function(done) { + it('calls callback when data tiles are loaded', async () => { fetchMock.mock(/map.json\?bbox/, { body: tileResponse, status: 200, @@ -313,13 +299,11 @@ describe('iD.serviceOsm', function () { var spy = sinon.spy(); connection.loadTiles(context.projection, spy); - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 500); + await setTimeout(500); + expect(spy).to.have.been.calledOnce; }); - it('#isDataLoaded', function(done) { + it('#isDataLoaded', async () => { fetchMock.mock(/map.json\?bbox/, { body: tileResponse, status: 200, @@ -338,11 +322,9 @@ describe('iD.serviceOsm', function () { connection.loadTiles(context.projection); - window.setTimeout(function() { - expect(fetchMock.called()).to.be.true; - expect(connection.isDataLoaded([-74.0444216, 40.6694299])).to.be.true; - done(); - }, 500); + await setTimeout(500); + expect(fetchMock.called()).to.be.true; + expect(connection.isDataLoaded([-74.0444216, 40.6694299])).to.be.true; }); }); @@ -364,7 +346,7 @@ describe('iD.serviceOsm', function () { ' ]' + '}'; - it('loads a node', function(done) { + it('loads a node', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/node/1.json', { body: nodeResponse, status: 200, @@ -372,14 +354,13 @@ describe('iD.serviceOsm', function () { }); var id = 'n1'; - connection.loadEntity(id, function(err, result) { - var entity = result.data.find(function(e) { return e.id === id; }); - expect(entity).to.be.an.instanceOf(iD.osmNode); - done(); - }); + const result = await promisify(connection.loadEntity).call(connection, id); + + var entity = result.data.find(function(e) { return e.id === id; }); + expect(entity).to.be.an.instanceOf(iD.osmNode); }); - it('loads a way', function(done) { + it('loads a way', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/way/1/full.json', { body: wayResponse, status: 200, @@ -387,14 +368,13 @@ describe('iD.serviceOsm', function () { }); var id = 'w1'; - connection.loadEntity(id, function(err, result) { - var entity = result.data.find(function(e) { return e.id === id; }); - expect(entity).to.be.an.instanceOf(iD.osmWay); - done(); - }); + const result = await promisify(connection.loadEntity).call(connection, id); + + var entity = result.data.find(function(e) { return e.id === id; }); + expect(entity).to.be.an.instanceOf(iD.osmWay); }); - it('does not ignore repeat requests', function(done) { + it('does not ignore repeat requests', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/node/1.json', { body: wayResponse, status: 200, @@ -402,16 +382,13 @@ describe('iD.serviceOsm', function () { }); var id = 'n1'; - connection.loadEntity(id, function(err1, result1) { - var entity1 = result1.data.find(function(e1) { return e1.id === id; }); - expect(entity1).to.be.an.instanceOf(iD.osmNode); + const result1 = await promisify(connection.loadEntity).call(connection, id); + var entity1 = result1.data.find(function(e1) { return e1.id === id; }); + expect(entity1).to.be.an.instanceOf(iD.osmNode); - connection.loadEntity(id, function(err2, result2) { - var entity2 = result2.data.find(function(e2) { return e2.id === id; }); - expect(entity2).to.be.an.instanceOf(iD.osmNode); - done(); - }); - }); + const result2 = await promisify(connection.loadEntity).call(connection, id); + var entity2 = result2.data.find(function(e2) { return e2.id === id; }); + expect(entity2).to.be.an.instanceOf(iD.osmNode); }); }); @@ -433,7 +410,7 @@ describe('iD.serviceOsm', function () { ' ]' + '}'; - it('loads a node', function(done) { + it('loads a node', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/node/1/1.json', { body: nodeResponse, status: 200, @@ -441,14 +418,13 @@ describe('iD.serviceOsm', function () { }); var id = 'n1'; - connection.loadEntityVersion(id, 1, function(err, result) { - var entity = result.data.find(function(e) { return e.id === id; }); - expect(entity).to.be.an.instanceOf(iD.osmNode); - done(); - }); + const result = await promisify(connection.loadEntityVersion).call(connection, id, 1); + + var entity = result.data.find(function(e) { return e.id === id; }); + expect(entity).to.be.an.instanceOf(iD.osmNode); }); - it('loads a way', function(done) { + it('loads a way', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/way/1/1.json', { body: wayResponse, status: 200, @@ -456,14 +432,13 @@ describe('iD.serviceOsm', function () { }); var id = 'w1'; - connection.loadEntityVersion(id, 1, function(err, result) { - var entity = result.data.find(function(e) { return e.id === id; }); - expect(entity).to.be.an.instanceOf(iD.osmWay); - done(); - }); + const result = await promisify(connection.loadEntityVersion).call(connection, id, 1); + + var entity = result.data.find(function(e) { return e.id === id; }); + expect(entity).to.be.an.instanceOf(iD.osmWay); }); - it('does not ignore repeat requests', function(done) { + it('does not ignore repeat requests', async () => { fetchMock.mock('https://www.openstreetmap.org/api/0.6/node/1/1.json', { body: nodeResponse, status: 200, @@ -471,16 +446,15 @@ describe('iD.serviceOsm', function () { }); var id = 'n1'; - connection.loadEntityVersion(id, 1, function(err1, result1) { - var entity1 = result1.data.find(function(e1) { return e1.id === id; }); - expect(entity1).to.be.an.instanceOf(iD.osmNode); + const result1 = await promisify(connection.loadEntityVersion).call(connection, id, 1); - connection.loadEntityVersion(id, 1, function(err2, result2) { - var entity2 = result2.data.find(function(e2) { return e2.id === id; }); - expect(entity2).to.be.an.instanceOf(iD.osmNode); - done(); - }); - }); + var entity1 = result1.data.find(function(e1) { return e1.id === id; }); + expect(entity1).to.be.an.instanceOf(iD.osmNode); + + const result2 = await promisify(connection.loadEntityVersion).call(connection, id, 1); + + var entity2 = result2.data.find(function(e2) { return e2.id === id; }); + expect(entity2).to.be.an.instanceOf(iD.osmNode); }); }); @@ -507,7 +481,7 @@ describe('iD.serviceOsm', function () { }); - it('loads user changesets', function(done) { + it('loads user changesets', async () => { var changesetsXML = '' + '' + '' + @@ -517,23 +491,23 @@ describe('iD.serviceOsm', function () { ''; login(); - connection.userChangesets(function(err, changesets) { - expect(changesets).to.deep.equal([{ - tags: { - comment: 'Caprice Court has been extended', - created_by: 'iD 2.0.0' - } - }]); - connection.logout(); - done(); - }); serverXHR.respondWith('GET', 'https://www.openstreetmap.org/api/0.6/changesets\\?user=1', [200, { 'Content-Type': 'text/xml' }, changesetsXML]); serverXHR.respond(); + + const changesets = await promisify(connection.userChangesets).call(connection); + + expect(changesets).to.deep.equal([{ + tags: { + comment: 'Caprice Court has been extended', + created_by: 'iD 2.0.0' + } + }]); + connection.logout(); }); - it('excludes changesets without comment tag', function(done) { + it('excludes changesets without comment tag', async () => { var changesetsXML = '' + '' + '' + @@ -546,23 +520,23 @@ describe('iD.serviceOsm', function () { ''; login(); - connection.userChangesets(function(err, changesets) { - expect(changesets).to.deep.equal([{ - tags: { - comment: 'Caprice Court has been extended', - created_by: 'iD 2.0.0' - } - }]); - connection.logout(); - done(); - }); serverXHR.respondWith('GET', 'https://www.openstreetmap.org/api/0.6/changesets\\?user=1', [200, { 'Content-Type': 'text/xml' }, changesetsXML]); serverXHR.respond(); + + const changesets = await promisify(connection.userChangesets).call(connection); + + expect(changesets).to.deep.equal([{ + tags: { + comment: 'Caprice Court has been extended', + created_by: 'iD 2.0.0' + } + }]); + connection.logout(); }); - it('excludes changesets with empty comment', function(done) { + it('excludes changesets with empty comment', async () => { var changesetsXML = '' + '' + '' + @@ -576,20 +550,20 @@ describe('iD.serviceOsm', function () { ''; login(); - connection.userChangesets(function(err, changesets) { - expect(changesets).to.deep.equal([{ - tags: { - comment: 'Caprice Court has been extended', - created_by: 'iD 2.0.0' - } - }]); - connection.logout(); - done(); - }); serverXHR.respondWith('GET', 'https://www.openstreetmap.org/api/0.6/changesets\\?user=1', [200, { 'Content-Type': 'text/xml' }, changesetsXML]); serverXHR.respond(); + + const changesets = await promisify(connection.userChangesets)(); + + expect(changesets).to.deep.equal([{ + tags: { + comment: 'Caprice Court has been extended', + created_by: 'iD 2.0.0' + } + }]); + connection.logout(); }); }); @@ -668,7 +642,7 @@ describe('iD.serviceOsm', function () { .clipExtent([[0,0], dimensions]); }); - it('fires loadedNotes when notes are loaded', function(done) { + it('fires loadedNotes when notes are loaded', async () => { fetchMock.mock(/notes\?/, { body: notesXML, status: 200, @@ -678,10 +652,8 @@ describe('iD.serviceOsm', function () { connection.on('loadedNotes', spy); connection.loadNotes(context.projection, {}); - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 500); + await setTimeout(500); + expect(spy).to.have.been.calledOnce; }); }); @@ -785,7 +757,7 @@ describe('iD.serviceOsm', function () { `; describe('#status', function() { - it('gets API status', function(done) { + it('gets API status', async () => { fetchMock.mock('https://www.openstreetmap.org/api/capabilities', { body: capabilitiesXML, status: 200, @@ -794,15 +766,13 @@ describe('iD.serviceOsm', function () { overwriteRoutes: true }); - connection.status(function (err, val) { - expect(val).to.eql('online'); - done(); - }); + const val = await promisify(connection.status).call(connection); + expect(val).to.eql('online'); }); }); describe('#imageryBlocklists', function() { - it('updates imagery blocklists', function(done) { + it('updates imagery blocklists', async () => { fetchMock.mock('https://www.openstreetmap.org/api/capabilities', { body: capabilitiesXML, status: 200, @@ -811,11 +781,9 @@ describe('iD.serviceOsm', function () { overwriteRoutes: true }); - connection.status(function() { - var blocklists = connection.imageryBlocklists(); - expect(blocklists).to.deep.equal([new RegExp('\.foo\.com'), new RegExp('\.bar\.org')]); - done(); - }); + await promisify(connection.status).call(connection); + var blocklists = connection.imageryBlocklists(); + expect(blocklists).to.deep.equal([new RegExp('\.foo\.com'), new RegExp('\.bar\.org')]); }); }); diff --git a/test/spec/services/osm_wikibase.js b/test/spec/services/osm_wikibase.js index ce736f477..52d94bc1a 100644 --- a/test/spec/services/osm_wikibase.js +++ b/test/spec/services/osm_wikibase.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceOsmWikibase', function () { var wikibase; @@ -262,7 +264,7 @@ describe('iD.serviceOsmWikibase', function () { }; describe('#getEntity', function () { - it('calls the given callback with the results of the getEntity data item query', function (done) { + it('calls the given callback with the results of the getEntity data item query', async () => { var callback = sinon.spy(); fetchMock.mock(/action=wbgetentities/, { body: JSON.stringify({ @@ -279,24 +281,22 @@ describe('iD.serviceOsmWikibase', function () { wikibase.getEntity({ key: 'amenity', value: 'parking', langCodes: ['fr'] }, callback); - window.setTimeout(function () { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - { - action: 'wbgetentities', - sites: 'wiki', - titles: 'Locale:fr|Key:amenity|Tag:amenity=parking', - languages: 'fr', - languagefallback: '1', - origin: '*', - format: 'json', - } - ); - expect(callback).to.have.been.calledWith(null, { - key: keyData(), - tag: tagData() - }); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + { + action: 'wbgetentities', + sites: 'wiki', + titles: 'Locale:fr|Key:amenity|Tag:amenity=parking', + languages: 'fr', + languagefallback: '1', + origin: '*', + format: 'json', + } + ); + expect(callback).to.have.been.calledWith(null, { + key: keyData(), + tag: tagData() + }); }); }); diff --git a/test/spec/services/streetside.js b/test/spec/services/streetside.js index 3caea8c71..c7596bb32 100644 --- a/test/spec/services/streetside.js +++ b/test/spec/services/streetside.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceStreetside', function() { var dimensions = [64, 64]; var context, streetside; @@ -47,7 +49,7 @@ describe('iD.serviceStreetside', function() { }); describe('#loadBubbles', function() { - it('fires loadedImages when bubbles are loaded', function(done) { + it('fires loadedImages when bubbles are loaded', async () => { // adjust projection so that only one tile is fetched // (JSONP hack will return the same data for every fetch) context.projection @@ -71,13 +73,11 @@ describe('iD.serviceStreetside', function() { streetside.loadBubbles(context.projection, 0); // 0 = don't fetch margin tiles - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.calledOnce; }); - it('does not load bubbles around null island', function(done) { + it('does not load bubbles around null island', async () => { context.projection .scale(iD.geoZoomToScale(18)) .translate([0, 0]) @@ -99,10 +99,8 @@ describe('iD.serviceStreetside', function() { streetside.loadBubbles(context.projection, 0); // 0 = don't fetch margin tiles - window.setTimeout(function() { - expect(spy).to.have.been.not.called; - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.not.called; }); }); diff --git a/test/spec/services/taginfo.js b/test/spec/services/taginfo.js index cdb35deac..558632bfc 100644 --- a/test/spec/services/taginfo.js +++ b/test/spec/services/taginfo.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceTaginfo', function() { var taginfo; @@ -33,7 +35,7 @@ describe('iD.serviceTaginfo', function() { } describe('#keys', function() { - it('calls the given callback with the results of the keys query', function(done) { + it('calls the given callback with the results of the keys query', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0}]}', status: 200, @@ -43,18 +45,16 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.keys({ query: 'amen' }, callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {query: 'amen', page: '1', rp: '10', sortname: 'count_all', sortorder: 'desc', lang: 'en'} - ); - expect(callback).to.have.been.calledWith( - null, [{'title':'amenity', 'value':'amenity'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {query: 'amen', page: '1', rp: '10', sortname: 'count_all', sortorder: 'desc', lang: 'en'} + ); + expect(callback).to.have.been.calledWith( + null, [{'title':'amenity', 'value':'amenity'}] + ); }); - it('includes popular keys', function(done) { + it('includes popular keys', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}', @@ -65,15 +65,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.keys({ query: 'amen' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'title':'amenity', 'value':'amenity'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'title':'amenity', 'value':'amenity'}] + ); }); - it('includes popular keys with an entity type filter', function(done) { + it('includes popular keys with an entity type filter', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}', @@ -84,15 +82,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.keys({ query: 'amen', filter: 'nodes' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'title':'amenity', 'value':'amenity'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'title':'amenity', 'value':'amenity'}] + ); }); - it('includes unpopular keys with a wiki page', function(done) { + it('includes unpopular keys with a wiki page', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":0.0, "in_wiki": true}]}', @@ -103,16 +99,14 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.keys({ query: 'amen' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith(null, [ - {'title':'amenity', 'value':'amenity'}, - {'title':'amenityother', 'value':'amenityother'} - ]); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith(null, [ + {'title':'amenity', 'value':'amenity'}, + {'title':'amenityother', 'value':'amenityother'} + ]); }); - it('sorts keys with \':\' below keys without \':\'', function(done) { + it('sorts keys with \':\' below keys without \':\'', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"key":"ref:bag","count_all":9790586,"count_all_fraction":0.0028},' + '{"key":"ref","count_all":7933528,"count_all_fraction":0.0023}]}', @@ -123,17 +117,15 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.keys({ query: 'ref' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'title':'ref', 'value':'ref'},{'title':'ref:bag', 'value':'ref:bag'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'title':'ref', 'value':'ref'},{'title':'ref:bag', 'value':'ref:bag'}] + ); }); }); describe('#multikeys', function() { - it('calls the given callback with the results of the multikeys query', function(done) { + it('calls the given callback with the results of the multikeys query', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":69593,"key":"recycling:glass","count_all_fraction":0.0}]}', status: 200, @@ -143,18 +135,16 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.multikeys({ query: 'recycling:' }, callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {query: 'recycling:', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en'} - ); - expect(callback).to.have.been.calledWith( - null, [{'title':'recycling:glass', 'value':'recycling:glass'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {query: 'recycling:', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en'} + ); + expect(callback).to.have.been.calledWith( + null, [{'title':'recycling:glass', 'value':'recycling:glass'}] + ); }); - it('excludes multikeys with extra colons', function(done) { + it('excludes multikeys with extra colons', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":4426,"key":"service:bicycle:retail","count_all_fraction":0.0},' + '{"count_all":22,"key":"service:bicycle:retail:ebikes","count_all_fraction":0.0}]}', @@ -165,15 +155,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.multikeys({ query: 'service:bicycle:' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'title':'service:bicycle:retail', 'value':'service:bicycle:retail'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'title':'service:bicycle:retail', 'value':'service:bicycle:retail'}] + ); }); - it('excludes multikeys with wrong prefix', function(done) { + it('excludes multikeys with wrong prefix', async () => { fetchMock.mock(/\/keys\/all/, { body: '{"data":[{"count_all":4426,"key":"service:bicycle:retail","count_all_fraction":0.0},' + '{"count_all":22,"key":"disused:service:bicycle","count_all_fraction":0.0}]}', @@ -184,17 +172,15 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.multikeys({ query: 'service:bicycle:' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'title':'service:bicycle:retail', 'value':'service:bicycle:retail'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'title':'service:bicycle:retail', 'value':'service:bicycle:retail'}] + ); }); }); describe('#values', function() { - it('calls the given callback with the results of the values query', function(done) { + it('calls the given callback with the results of the values query', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "count":1000}]}', status: 200, @@ -204,18 +190,16 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'amenity', query: 'par' }, callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {key: 'amenity', query: 'par', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en'} - ); - expect(callback).to.have.been.calledWith( - null, [{'value':'parking','title':'A place for parking cars'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {key: 'amenity', query: 'par', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en'} + ); + expect(callback).to.have.been.calledWith( + null, [{'value':'parking','title':'A place for parking cars'}] + ); }); - it('includes popular values', function(done) { + it('includes popular values', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "count":1000},' + '{"value":"party","description":"A place for partying", "count":1}]}', @@ -226,15 +210,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'amenity', query: 'par' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'parking','title':'A place for parking cars'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'parking','title':'A place for parking cars'}] + ); }); - it('does not get values for extremely unpopular keys', function(done) { + it('does not get values for extremely unpopular keys', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"Rue Pasteur","description":"", "count":3},' + '{"value":"Via Trieste","description":"", "count":1}]}', @@ -245,13 +227,11 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'name', query: 'ste' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith(null, []); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith(null, []); }); - it('includes unpopular values with a wiki page', function(done) { + it('includes unpopular values with a wiki page', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"party","description":"A place for partying", "count":1, "in_wiki": true}]}', status: 200, @@ -261,15 +241,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'amenity', query: 'par' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'party','title':'A place for partying'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'party','title':'A place for partying'}] + ); }); - it('excludes values with capital letters and some punctuation', function(done) { + it('excludes values with capital letters and some punctuation', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "count":2000},' + '{"value":"PArking","description":"A common misspelling", "count":200},' @@ -283,15 +261,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'amenity', query: 'par' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'parking','title':'A place for parking cars'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'parking','title':'A place for parking cars'}] + ); }); - it('includes network values with capital letters and some punctuation', function(done) { + it('includes network values with capital letters and some punctuation', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"US:TX:FM","description":"Farm to Market Roads in the U.S. state of Texas.", "count":34000},' + '{"value":"US:KY","description":"Primary and secondary state highways in the U.S. state of Kentucky.", "count":31000},' @@ -305,19 +281,17 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'network', query: 'us' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith(null, [ - {'value':'US:TX:FM','title':'Farm to Market Roads in the U.S. state of Texas.'}, - {'value':'US:KY','title':'Primary and secondary state highways in the U.S. state of Kentucky.'}, - {'value':'US:US','title':'U.S. routes in the United States.'}, - {'value':'US:I','title':'Interstate highways in the United States.'}, - {'value':'US:MD','title':'State highways in the U.S. state of Maryland.'} - ]); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith(null, [ + {'value':'US:TX:FM','title':'Farm to Market Roads in the U.S. state of Texas.'}, + {'value':'US:KY','title':'Primary and secondary state highways in the U.S. state of Kentucky.'}, + {'value':'US:US','title':'U.S. routes in the United States.'}, + {'value':'US:I','title':'Interstate highways in the United States.'}, + {'value':'US:MD','title':'State highways in the U.S. state of Maryland.'} + ]); }); - it('includes biological genus values with capital letters', function(done) { + it('includes biological genus values with capital letters', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"Quercus","description":"Oak", "count": 1000}]}', status: 200, @@ -327,15 +301,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'genus', query: 'qu' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'Quercus','title':'Oak'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'Quercus','title':'Oak'}] + ); }); - it('includes biological taxon values with capital letters', function(done) { + it('includes biological taxon values with capital letters', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"Quercus robur","description":"Oak", "count": 1000}]}', status: 200, @@ -345,15 +317,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'taxon', query: 'qu' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'Quercus robur','title':'Oak'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'Quercus robur','title':'Oak'}] + ); }); - it('includes biological species values with capital letters', function(done) { + it('includes biological species values with capital letters', async () => { fetchMock.mock(/\/key\/values/, { body: '{"data":[{"value":"Quercus robur","description":"Oak", "count": 1000}]}', status: 200, @@ -363,17 +333,15 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.values({ key: 'species', query: 'qu' }, callback); - window.setTimeout(function() { - expect(callback).to.have.been.calledWith( - null, [{'value':'Quercus robur','title':'Oak'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(callback).to.have.been.calledWith( + null, [{'value':'Quercus robur','title':'Oak'}] + ); }); }); describe('#roles', function() { - it('calls the given callback with the results of the roles query', function(done) { + it('calls the given callback with the results of the roles query', async () => { fetchMock.mock(/\/relation\/roles/, { body: '{"data":[{"role":"stop","count_relation_members_fraction":0.1757},' + '{"role":"south","count_relation_members_fraction":0.0035}]}', @@ -384,21 +352,19 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.roles({ rtype: 'route', query: 's', geometry: 'relation' }, callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {rtype: 'route', query: 's', page: '1', rp: '25', sortname: 'count_relation_members', sortorder: 'desc', lang: 'en'} - ); - expect(callback).to.have.been.calledWith(null, [ - {'value': 'stop', 'title': 'stop'}, - {'value': 'south', 'title': 'south'} - ]); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {rtype: 'route', query: 's', page: '1', rp: '25', sortname: 'count_relation_members', sortorder: 'desc', lang: 'en'} + ); + expect(callback).to.have.been.calledWith(null, [ + {'value': 'stop', 'title': 'stop'}, + {'value': 'south', 'title': 'south'} + ]); }); }); describe('#docs', function() { - it('calls the given callback with the results of the docs query', function(done) { + it('calls the given callback with the results of the docs query', async () => { fetchMock.mock(/\/tag\/wiki_page/, { body: '{"data":[{"on_way":false,"lang":"en","on_area":true,"image":"File:Car park2.jpg"}]}', status: 200, @@ -408,15 +374,13 @@ describe('iD.serviceTaginfo', function() { var callback = sinon.spy(); taginfo.docs({ key: 'amenity', value: 'parking' }, callback); - window.setTimeout(function() { - expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( - {key: 'amenity', value: 'parking'} - ); - expect(callback).to.have.been.calledWith( - null, [{'on_way':false,'lang':'en','on_area':true,'image':'File:Car park2.jpg'}] - ); - done(); - }, 50); + await setTimeout(50); + expect(parseQueryString(fetchMock.calls()[0][0])).to.eql( + {key: 'amenity', value: 'parking'} + ); + expect(callback).to.have.been.calledWith( + null, [{'on_way':false,'lang':'en','on_area':true,'image':'File:Car park2.jpg'}] + ); }); }); diff --git a/test/spec/services/vegbilder.js b/test/spec/services/vegbilder.js index d340aeb64..2225b6566 100644 --- a/test/spec/services/vegbilder.js +++ b/test/spec/services/vegbilder.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.serviceVegbilder', function() { const dimensions = [64, 64]; const testImages = [{ @@ -248,7 +250,7 @@ describe('iD.serviceVegbilder', function() { vegbilder.on('loadedImages', spy); vegbilder.loadImages(context, 0); - await new Promise((resolve) => { window.setTimeout(resolve, 200); }); + await setTimeout(200); expect(spy).to.have.been.not.called; expect(fetchMock.calls().length).to.eql(0); diff --git a/test/spec/svg/data.js b/test/spec/svg/data.js index 02a1e71d5..bd990d6a4 100644 --- a/test/spec/svg/data.js +++ b/test/spec/svg/data.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.svgData', function () { var context; var surface; @@ -114,72 +116,66 @@ describe('iD.svgData', function () { }); describe('#fileList', function() { - it('handles gpx files', function (done) { + it('handles gpx files', async () => { var files = [ makeFile(gpx, 'test.gpx', 'application/gpx+xml') ]; var render = iD.svgData(projection, context, dispatch); var spy = sinon.spy(); dispatch.on('change', spy); render.fileList(files); - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - surface.call(render); - var path; - path = surface.selectAll('path.shadow'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - path = surface.selectAll('path.stroke'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.calledOnce; + surface.call(render); + var path; + path = surface.selectAll('path.shadow'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); + path = surface.selectAll('path.stroke'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); }); - it('handles kml files', function (done) { + it('handles kml files', async () => { var files = [ makeFile(kml, 'test.kml', 'application/vnd.google-earth.kml+xml') ]; var render = iD.svgData(projection, context, dispatch); var spy = sinon.spy(); dispatch.on('change', spy); render.fileList(files); - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - surface.call(render); - var path; - path = surface.selectAll('path.shadow'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - path = surface.selectAll('path.stroke'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.calledOnce; + surface.call(render); + var path; + path = surface.selectAll('path.shadow'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); + path = surface.selectAll('path.stroke'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); }); - it('handles geojson files', function (done) { + it('handles geojson files', async () => { var files = [ makeFile(geojson, 'test.geojson', 'application/vnd.geo+json') ]; var render = iD.svgData(projection, context, dispatch); var spy = sinon.spy(); dispatch.on('change', spy); render.fileList(files); - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - surface.call(render); - var path; - path = surface.selectAll('path.shadow'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - path = surface.selectAll('path.stroke'); - expect(path.nodes().length).to.eql(1); - expect(path.attr('d')).to.match(/^M.*z$/); - expect(render.geojson().features[0].properties.osm_id).to.be.a('string'); - expect(render.geojson().features[0].properties.flag).to.be.a('string'); - expect(render.geojson().features[0].properties.list).to.be.a('string'); - expect(render.geojson().features[0].properties.null).to.be.a('string'); - expect(render.geojson().features[0].properties.object).to.be.a('string'); - done(); - }, 200); + await setTimeout(200); + expect(spy).to.have.been.calledOnce; + surface.call(render); + var path; + path = surface.selectAll('path.shadow'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); + path = surface.selectAll('path.stroke'); + expect(path.nodes().length).to.eql(1); + expect(path.attr('d')).to.match(/^M.*z$/); + expect(render.geojson().features[0].properties.osm_id).to.be.a('string'); + expect(render.geojson().features[0].properties.flag).to.be.a('string'); + expect(render.geojson().features[0].properties.list).to.be.a('string'); + expect(render.geojson().features[0].properties.null).to.be.a('string'); + expect(render.geojson().features[0].properties.object).to.be.a('string'); }); }); diff --git a/test/spec/ui/combobox.js b/test/spec/ui/combobox.js index 4123710f6..8dffc8209 100644 --- a/test/spec/ui/combobox.js +++ b/test/spec/ui/combobox.js @@ -231,28 +231,24 @@ describe('uiCombobox', function() { expect(input.property('value')).to.equal('foobar'); }); - it('emits accepted event with selected datum on ⇥', function(done) { - combobox.on('accept', function(d) { - expect(d).to.eql({title: 'bar', value: 'bar'}); - combobox.on('accept', null); - done(); - }); + it('emits accepted event with selected datum on ⇥', async () => { + const d = new Promise(cb => { combobox.on('accept', cb); }); input.call(combobox.data(data)); focusTypeahead(input); simulateKeypress('b'); simulateKeypress('⇥'); + expect(await d).to.eql({title: 'bar', value: 'bar'}); + combobox.on('accept', null); }); - it('emits accepted event with selected datum on ↩', function(done) { - combobox.on('accept', function(d) { - expect(d).to.eql({title: 'bar', value: 'bar'}); - combobox.on('accept', null); - done(); - }); + it('emits accepted event with selected datum on ↩', async () => { + const d = new Promise(cb => { combobox.on('accept', cb); }); input.call(combobox.data(data)); focusTypeahead(input); simulateKeypress('b'); simulateKeypress('↩'); + expect(await d).to.eql({title: 'bar', value: 'bar'}); + combobox.on('accept', null); }); it('emits cancel event on ⎋', function() { diff --git a/test/spec/ui/confirm.js b/test/spec/ui/confirm.js index db37195b3..1d3359d35 100644 --- a/test/spec/ui/confirm.js +++ b/test/spec/ui/confirm.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiConfirm', function () { var elem; @@ -37,55 +39,45 @@ describe('iD.uiConfirm', function () { expect(selection.selectAll('div.content div.buttons button.action').size()).to.equal(1); }); - it('can be dismissed by calling close function', function (done) { + it('can be dismissed by calling close function', async () => { var selection = iD.uiConfirm(elem); selection.close(); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by clicking the close button', function (done) { + it('can be dismissed by clicking the close button', async () => { var selection = iD.uiConfirm(elem); happen.click(selection.select('button.close').node()); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by pressing escape', function (done) { + it('can be dismissed by pressing escape', async () => { var selection = iD.uiConfirm(elem); happen.keydown(document, {keyCode: 27}); happen.keyup(document, {keyCode: 27}); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by pressing backspace', function (done) { + it('can be dismissed by pressing backspace', async () => { var selection = iD.uiConfirm(elem); happen.keydown(document, {keyCode: 8}); happen.keyup(document, {keyCode: 8}); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by clicking the ok button', function (done) { + it('can be dismissed by clicking the ok button', async () => { var selection = iD.uiConfirm(elem).okButton(); happen.click(selection.select('div.content div.buttons button.action').node()); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); }); diff --git a/test/spec/ui/fields/localized.js b/test/spec/ui/fields/localized.js index 24788513f..bde9b6b9c 100644 --- a/test/spec/ui/fields/localized.js +++ b/test/spec/ui/fields/localized.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiFieldLocalized', function() { var context, selection, field; @@ -22,158 +24,138 @@ describe('iD.uiFieldLocalized', function() { }); - it('adds a blank set of fields when the + button is clicked', function(done) { + it('adds a blank set of fields when the + button is clicked', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - happen.click(selection.selectAll('.localized-add').node()); - expect(selection.selectAll('.localized-lang').nodes().length).to.equal(1); - expect(selection.selectAll('.localized-value').nodes().length).to.equal(1); - done(); - }, 20); + await setTimeout(20); + selection.call(localized); + happen.click(selection.selectAll('.localized-add').node()); + expect(selection.selectAll('.localized-lang').nodes().length).to.equal(1); + expect(selection.selectAll('.localized-value').nodes().length).to.equal(1); }); - it('doesn\'t create a tag when the value is empty', function(done) { + it('doesn\'t create a tag when the value is empty', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - happen.click(selection.selectAll('.localized-add').node()); + await setTimeout(20); + selection.call(localized); + happen.click(selection.selectAll('.localized-add').node()); - localized.on('change', function(tags) { - expect(tags).to.eql({}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'blur'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'blur'}); }); - it('doesn\'t create a tag when the name is empty', function(done) { + it('doesn\'t create a tag when the name is empty', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - happen.click(selection.selectAll('.localized-add').node()); + await setTimeout(20); + selection.call(localized); + happen.click(selection.selectAll('.localized-add').node()); - localized.on('change', function(tags) { - expect(tags).to.eql({}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); - happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); - happen.once(selection.selectAll('.localized-value').node(), {type: 'blur'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); + happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); + happen.once(selection.selectAll('.localized-value').node(), {type: 'blur'}); }); - it('creates a tag after setting language then value', function(done) { + it('creates a tag after setting language then value', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - happen.click(selection.selectAll('.localized-add').node()); + await setTimeout(20); + selection.call(localized); + happen.click(selection.selectAll('.localized-add').node()); - iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); + iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); - localized.on('change', function(tags) { - expect(tags).to.eql({'name:de': 'Value'}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({'name:de': 'Value'}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); - happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); + happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); }); - it('creates a tag after setting value then language', function(done) { + it('creates a tag after setting value then language', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - happen.click(selection.selectAll('.localized-add').node()); + await setTimeout(20); + selection.call(localized); + happen.click(selection.selectAll('.localized-add').node()); - iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); - happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); + iD.utilGetSetValue(selection.selectAll('.localized-value'), 'Value'); + happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); - localized.on('change', function(tags) { - expect(tags).to.eql({'name:de': 'Value'}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({'name:de': 'Value'}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'Deutsch'); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); }); - it('changes an existing language', function(done) { + it('changes an existing language', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - localized.tags({'name:de': 'Value'}); + await setTimeout(20); + selection.call(localized); + localized.tags({'name:de': 'Value'}); - localized.on('change', function(tags) { - expect(tags).to.eql({ - 'name:de': undefined, - 'name:en': 'Value'}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({ + 'name:de': undefined, + 'name:en': 'Value'}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'English'); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-lang'), 'English'); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); }); - it('ignores similar keys like `old_name`', function(done) { + it('ignores similar keys like `old_name`', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - localized.tags({'old_name:de': 'Value'}); + await setTimeout(20); + selection.call(localized); + localized.tags({'old_name:de': 'Value'}); - expect(selection.selectAll('.localized-lang').empty()).to.be.ok; - expect(selection.selectAll('.localized-value').empty()).to.be.ok; - done(); - }, 20); + expect(selection.selectAll('.localized-lang').empty()).to.be.ok; + expect(selection.selectAll('.localized-value').empty()).to.be.ok; }); - it('removes the tag when the language is emptied', function(done) { + it('removes the tag when the language is emptied', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - localized.tags({'name:de': 'Value'}); + await setTimeout(20); + selection.call(localized); + localized.tags({'name:de': 'Value'}); - localized.on('change', function(tags) { - expect(tags).to.eql({'name:de': undefined}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({'name:de': undefined}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-lang'), ''); - happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-lang'), ''); + happen.once(selection.selectAll('.localized-lang').node(), {type: 'change'}); }); - it('removes the tag when the value is emptied', function(done) { + it('removes the tag when the value is emptied', async () => { var localized = iD.uiFieldLocalized(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(localized); - localized.tags({'name:de': 'Value'}); + await setTimeout(20); + selection.call(localized); + localized.tags({'name:de': 'Value'}); - localized.on('change', function(tags) { - expect(tags).to.eql({'name:de': undefined}); - }); + localized.on('change', function(tags) { + expect(tags).to.eql({'name:de': undefined}); + }); - iD.utilGetSetValue(selection.selectAll('.localized-value'), ''); - happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); - done(); - }, 20); + iD.utilGetSetValue(selection.selectAll('.localized-value'), ''); + happen.once(selection.selectAll('.localized-value').node(), {type: 'change'}); }); - it('has a lang attribute on an existing multilingual name field', function(done) { - var localized = iD.uiFieldLocalized(field, context); - localized.tags({'name:de': 'Value'}); - window.setTimeout(function() { + it('has a lang attribute on an existing multilingual name field', async () => { + var localized = iD.uiFieldLocalized(field, context); + localized.tags({'name:de': 'Value'}); + await setTimeout(20); selection.call(localized); expect(selection.selectAll('.localized-value').attr('lang')).to.eql('de'); - done(); - }, 20); }); }); diff --git a/test/spec/ui/fields/wikipedia.js b/test/spec/ui/fields/wikipedia.js index c5790fa1a..5c8399abb 100644 --- a/test/spec/ui/fields/wikipedia.js +++ b/test/spec/ui/fields/wikipedia.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiFieldWikipedia', function() { var entity, context, selection, field; @@ -60,119 +62,104 @@ describe('iD.uiFieldWikipedia', function() { } } - it('recognizes lang:title format', function(done) { + it('recognizes lang:title format', async () => { var wikipedia = iD.uiFieldWikipedia(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(wikipedia); - wikipedia.tags({wikipedia: 'en:Title'}); + await setTimeout(20); + selection.call(wikipedia); + wikipedia.tags({wikipedia: 'en:Title'}); - expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('English'); - expect(iD.utilGetSetValue(selection.selectAll('.wiki-title'))).to.equal('Title'); - done(); - }, 20); + expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('English'); + expect(iD.utilGetSetValue(selection.selectAll('.wiki-title'))).to.equal('Title'); }); - it('sets language, value', function(done) { + it('sets language, value', async () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); - window.setTimeout(function() { // async, so data will be available - wikipedia.on('change', changeTags); - selection.call(wikipedia); + await setTimeout(20); + wikipedia.on('change', changeTags); + selection.call(wikipedia); - var spy = sinon.spy(); - wikipedia.on('change.spy', spy); + var spy = sinon.spy(); + wikipedia.on('change.spy', spy); - iD.utilGetSetValue(selection.selectAll('.wiki-lang'), 'Deutsch'); - happen.once(selection.selectAll('.wiki-lang').node(), { type: 'change' }); - happen.once(selection.selectAll('.wiki-lang').node(), { type: 'blur' }); + iD.utilGetSetValue(selection.selectAll('.wiki-lang'), 'Deutsch'); + happen.once(selection.selectAll('.wiki-lang').node(), { type: 'change' }); + happen.once(selection.selectAll('.wiki-lang').node(), { type: 'blur' }); - iD.utilGetSetValue(selection.selectAll('.wiki-title'), 'Title'); - happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' }); - happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' }); + iD.utilGetSetValue(selection.selectAll('.wiki-title'), 'Title'); + happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' }); + happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' }); - expect(spy.callCount).to.equal(4); - expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: undefined}); // lang on change - expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: undefined}); // lang on blur - expect(spy.getCall(2)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // title on change - expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // title on blur - done(); - }, 20); + expect(spy.callCount).to.equal(4); + expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: undefined}); // lang on change + expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: undefined}); // lang on blur + expect(spy.getCall(2)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // title on change + expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // title on blur }); - it('recognizes pasted URLs', function(done) { + it('recognizes pasted URLs', async () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); - window.setTimeout(function() { // async, so data will be available - wikipedia.on('change', changeTags); - selection.call(wikipedia); + await setTimeout(20); + wikipedia.on('change', changeTags); + selection.call(wikipedia); - iD.utilGetSetValue(selection.selectAll('.wiki-title'), 'http://de.wikipedia.org/wiki/Title'); - happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' }); + iD.utilGetSetValue(selection.selectAll('.wiki-title'), 'http://de.wikipedia.org/wiki/Title'); + happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' }); - expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('Deutsch'); - expect(iD.utilGetSetValue(selection.selectAll('.wiki-title'))).to.equal('Title'); - done(); - }, 20); + expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('Deutsch'); + expect(iD.utilGetSetValue(selection.selectAll('.wiki-title'))).to.equal('Title'); }); describe('encodePath', function() { - it('returns an encoded URI component that contains the title with spaces replaced by underscores', function(done) { + it('returns an encoded URI component that contains the title with spaces replaced by underscores', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); expect(wikipedia.encodePath('? (film)', undefined)).to.equal('%3F_(film)'); - done(); }); - it('returns an encoded URI component that includes an anchor fragment', function(done) { + it('returns an encoded URI component that includes an anchor fragment', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); // this can be tested manually by entering '? (film)#Themes and style in the search box before focusing out' expect(wikipedia.encodePath('? (film)', 'Themes and style')).to.equal('%3F_(film)#Themes_and_style'); - done(); }); }); describe('encodeURIAnchorFragment', function() { - it('returns an encoded URI anchor fragment', function(done) { + it('returns an encoded URI anchor fragment', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); // this can be similarly tested by entering 'Section#Arts, entertainment and media' in the search box before focusing out' expect(wikipedia.encodeURIAnchorFragment('Theme?')).to.equal('#Theme%3F'); - done(); }); - it('replaces all whitespace characters with underscore', function(done) { + it('replaces all whitespace characters with underscore', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); expect(wikipedia.encodeURIAnchorFragment('Themes And Styles')).to.equal('#Themes_And_Styles'); - done(); }); - it('encodes % characters, does not replace them with a dot', function(done) { + it('encodes % characters, does not replace them with a dot', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); expect(wikipedia.encodeURIAnchorFragment('Is%this_100% correct')).to.equal('#Is%25this_100%25_correct'); - done(); }); - it('encodes characters that are URI encoded characters', function (done) { + it('encodes characters that are URI encoded characters', () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); expect(wikipedia.encodeURIAnchorFragment('Section %20%25')).to.equal('#Section_%2520%2525'); - done(); }); }); // note - currently skipping the tests that use `options` to delay responses - it('preserves existing language', function(done) { + it('preserves existing language', async () => { var wikipedia1 = iD.uiFieldWikipedia(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(wikipedia1); - iD.utilGetSetValue(selection.selectAll('.wiki-lang'), 'Deutsch'); + await setTimeout(20); + selection.call(wikipedia1); + iD.utilGetSetValue(selection.selectAll('.wiki-lang'), 'Deutsch'); - var wikipedia2 = iD.uiFieldWikipedia(field, context); - window.setTimeout(function() { // async, so data will be available - selection.call(wikipedia2); - wikipedia2.tags({}); - expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('Deutsch'); - done(); - }, 20); - }, 20); + var wikipedia2 = iD.uiFieldWikipedia(field, context); + await setTimeout(20); + selection.call(wikipedia2); + wikipedia2.tags({}); + expect(iD.utilGetSetValue(selection.selectAll('.wiki-lang'))).to.equal('Deutsch'); }); - it.skip('does not set delayed wikidata tag if graph has changed', function(done) { + it.skip('does not set delayed wikidata tag if graph has changed', async () => { var wikipedia = iD.uiFieldWikipedia(field, context).entityIDs([entity.id]); wikipedia.on('change', changeTags); selection.call(wikipedia); @@ -227,16 +214,13 @@ describe('iD.uiFieldWikipedia', function() { // t90: at t30 + 60ms (delay), wikidata SHOULD be set because graph is unchanged. // t100: check that wikidata has changed - window.setTimeout(function() { - expect(context.entity(entity.id).tags.wikidata).to.equal('Q216353'); - - expect(spy.callCount).to.equal(4); - expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Skip' }); // 'Skip' on change - expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Skip' }); // 'Skip' on blur - expect(spy.getCall(2)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on change +10ms - expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur +10ms - done(); - }, 100); + await setTimeout(100); + expect(context.entity(entity.id).tags.wikidata).to.equal('Q216353'); + expect(spy.callCount).to.equal(4); + expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Skip' }); // 'Skip' on change + expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Skip' }); // 'Skip' on blur + expect(spy.getCall(2)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on change +10ms + expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur +10ms }); }); diff --git a/test/spec/ui/flash.js b/test/spec/ui/flash.js index 3a929f834..40dd8f5fe 100644 --- a/test/spec/ui/flash.js +++ b/test/spec/ui/flash.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiFlash', function () { var context; @@ -24,16 +26,14 @@ describe('iD.uiFlash', function () { expect(footerWrap.classed('footer-hide')).to.be.ok; }); - it('flash goes away', function(done) { + it('flash goes away', async () => { iD.uiFlash(context).duration(200)(); - window.setTimeout(function() { - d3.timerFlush(); - var flashWrap = d3.selectAll('.flash-wrap'); - var footerWrap = d3.selectAll('.main-footer-wrap'); - expect(flashWrap.classed('footer-hide')).to.be.ok; - expect(footerWrap.classed('footer-show')).to.be.ok; - done(); - }, 225); + await setTimeout(225); + d3.timerFlush(); + var flashWrap = d3.selectAll('.flash-wrap'); + var footerWrap = d3.selectAll('.main-footer-wrap'); + expect(flashWrap.classed('footer-hide')).to.be.ok; + expect(footerWrap.classed('footer-show')).to.be.ok; }); }); diff --git a/test/spec/ui/modal.js b/test/spec/ui/modal.js index e542a11c0..a92e5f8a9 100644 --- a/test/spec/ui/modal.js +++ b/test/spec/ui/modal.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiModal', function () { var elem; @@ -22,46 +24,38 @@ describe('iD.uiModal', function () { expect(selection.selectAll('div.content').size()).to.equal(1); }); - it('can be dismissed by calling close function', function (done) { + it('can be dismissed by calling close function', async () => { var selection = iD.uiModal(elem); selection.close(); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by clicking the close button', function (done) { + it('can be dismissed by clicking the close button', async () => { var selection = iD.uiModal(elem); happen.click(selection.select('button.close').node()); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by pressing escape', function (done) { + it('can be dismissed by pressing escape', async () => { var selection = iD.uiModal(elem); happen.keydown(document, {keyCode: 27}); happen.keyup(document, {keyCode: 27}); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); - it('can be dismissed by pressing backspace', function (done) { + it('can be dismissed by pressing backspace', async () => { var selection = iD.uiModal(elem); happen.keydown(document, {keyCode: 8}); happen.keyup(document, {keyCode: 8}); - window.setTimeout(function() { - d3.timerFlush(); - expect(selection.node().parentNode).to.be.null; - done(); - }, 275); + await setTimeout(275); + d3.timerFlush(); + expect(selection.node().parentNode).to.be.null; }); }); diff --git a/test/spec/ui/sections/raw_tag_editor.js b/test/spec/ui/sections/raw_tag_editor.js index e34c4f575..18e94f878 100644 --- a/test/spec/ui/sections/raw_tag_editor.js +++ b/test/spec/ui/sections/raw_tag_editor.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.uiSectionRawTagEditor', function() { var taglist, element, entity, context; @@ -39,42 +41,36 @@ describe('iD.uiSectionRawTagEditor', function() { expect(element.select('.tag-list').selectAll('input.key').property('value')).to.be.empty; }); - it('adds tags when clicking the add button', function (done) { + it('adds tags when clicking the add button', async () => { happen.click(element.selectAll('button.add-tag').node()); - setTimeout(function() { - expect(element.select('.tag-list').selectAll('input').nodes()[2].value).to.be.empty; - expect(element.select('.tag-list').selectAll('input').nodes()[3].value).to.be.empty; - done(); - }, 20); + await setTimeout(20); + expect(element.select('.tag-list').selectAll('input').nodes()[2].value).to.be.empty; + expect(element.select('.tag-list').selectAll('input').nodes()[3].value).to.be.empty; }); - it('removes tags when clicking the remove button', function (done) { - taglist.on('change', function(entityIDs, tags) { - expect(tags).to.eql({highway: undefined}); - done(); - }); + it('removes tags when clicking the remove button', async () => { iD.utilTriggerEvent(element.selectAll('button.remove'), 'mousedown', { button: 0 }); + const tags = await new Promise(cb => { + taglist.on('change', (_, tags) => cb(tags)); + }); + expect(tags).to.eql({highway: undefined}); }); - it('adds tags when pressing the TAB key on last input.value', function (done) { + it('adds tags when pressing the TAB key on last input.value', async () => { expect(element.selectAll('.tag-list li').nodes().length).to.eql(1); var input = d3.select('.tag-list li:last-child input.value').nodes()[0]; happen.keydown(d3.select(input).node(), {keyCode: 9}); - setTimeout(function() { - expect(element.selectAll('.tag-list li').nodes().length).to.eql(2); - expect(element.select('.tag-list').selectAll('input').nodes()[2].value).to.be.empty; - expect(element.select('.tag-list').selectAll('input').nodes()[3].value).to.be.empty; - done(); - }, 20); + await setTimeout(20); + expect(element.selectAll('.tag-list li').nodes().length).to.eql(2); + expect(element.select('.tag-list').selectAll('input').nodes()[2].value).to.be.empty; + expect(element.select('.tag-list').selectAll('input').nodes()[3].value).to.be.empty; }); - it('does not add a tag when pressing TAB while shift is pressed', function (done) { + it('does not add a tag when pressing TAB while shift is pressed', async () => { expect(element.selectAll('.tag-list li').nodes().length).to.eql(1); var input = d3.select('.tag-list li:last-child input.value').nodes()[0]; happen.keydown(d3.select(input).node(), {keyCode: 9, shiftKey: true}); - setTimeout(function() { - expect(element.selectAll('.tag-list li').nodes().length).to.eql(1); - done(); - }, 20); + await setTimeout(20); + expect(element.selectAll('.tag-list li').nodes().length).to.eql(1); }); }); diff --git a/test/spec/validations/mutually_exclusive_tags.js b/test/spec/validations/mutually_exclusive_tags.js index 1639ea701..515240a3f 100644 --- a/test/spec/validations/mutually_exclusive_tags.js +++ b/test/spec/validations/mutually_exclusive_tags.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.validations.mutually_exclusive_tags', function () { var context; @@ -22,71 +24,61 @@ describe('iD.validations.mutually_exclusive_tags', function () { } - it('has no errors on init', function(done) { + it('has no errors on init', async () => { var validator = iD.validationMutuallyExclusiveTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('has no errors on good tags', function(done) { + it('has no errors on good tags', async () => { createNode({'name': 'Trader Joe', 'not:name': 'Trader Jane'}); var validator = iD.validationMutuallyExclusiveTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('flags mutually exclusive tags', function(done) { + it('flags mutually exclusive tags', async () => { createNode({'name': 'Trader Joe', 'noname': 'yes'}); var validator = iD.validationMutuallyExclusiveTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('mutually_exclusive_tags'); - expect(issue.subtype).to.eql('default'); - expect(issue.severity).to.eql('warning'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('n-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('mutually_exclusive_tags'); + expect(issue.subtype).to.eql('default'); + expect(issue.severity).to.eql('warning'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('n-1'); }); - it('flags feature with a mutually exclusive `not:name` value', function(done) { + it('flags feature with a mutually exclusive `not:name` value', async () => { createNode({ shop: 'supermarket', name: 'Lous', 'not:name': 'Lous' }); var validator = iD.validationMutuallyExclusiveTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('mutually_exclusive_tags'); - expect(issue.subtype).to.eql('same_value'); - expect(issue.severity).to.eql('warning'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('n-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('mutually_exclusive_tags'); + expect(issue.subtype).to.eql('same_value'); + expect(issue.severity).to.eql('warning'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('n-1'); }); - it('flags feature with a mutually exclusive semicolon-separated `not:name` value', function(done) { + it('flags feature with a mutually exclusive semicolon-separated `not:name` value', async () => { createNode({ shop: 'supermarket', name: 'Lous', 'not:name': 'Louis\';Lous;Louis\'s' }); var validator = iD.validationMutuallyExclusiveTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('mutually_exclusive_tags'); - expect(issue.subtype).to.eql('same_value'); - expect(issue.severity).to.eql('warning'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('n-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('mutually_exclusive_tags'); + expect(issue.subtype).to.eql('same_value'); + expect(issue.severity).to.eql('warning'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('n-1'); }); }); diff --git a/test/spec/validations/outdated_tags.js b/test/spec/validations/outdated_tags.js index 0a64a11bb..1d2d04c26 100644 --- a/test/spec/validations/outdated_tags.js +++ b/test/spec/validations/outdated_tags.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.validations.outdated_tags', function () { var context; @@ -55,74 +57,62 @@ describe('iD.validations.outdated_tags', function () { return issues; } - it('has no errors on init', function(done) { + it('has no errors on init', async () => { var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('has no errors on good tags', function(done) { + it('has no errors on good tags', async () => { createWay({'highway': 'unclassified'}); var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('flags deprecated tag with replacement', function(done) { + it('flags deprecated tag with replacement', async () => { createWay({'highway': 'ford'}); var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('outdated_tags'); - expect(issue.subtype).to.eql('deprecated_tags'); - expect(issue.severity).to.eql('warning'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('outdated_tags'); + expect(issue.subtype).to.eql('deprecated_tags'); + expect(issue.severity).to.eql('warning'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags deprecated tag with no replacement', function(done) { + it('flags deprecated tag with no replacement', async () => { createWay({'highway': 'no'}); var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('outdated_tags'); - expect(issue.subtype).to.eql('deprecated_tags'); - expect(issue.severity).to.eql('warning'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('outdated_tags'); + expect(issue.subtype).to.eql('deprecated_tags'); + expect(issue.severity).to.eql('warning'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('ignores way with no relations', function(done) { + it('ignores way with no relations', async () => { createWay({}); var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores multipolygon tagged on the relation', function(done) { + it('ignores multipolygon tagged on the relation', async () => { createRelation({}, { type: 'multipolygon', building: 'yes' }); var validator = iD.validationOutdatedTags(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); }); diff --git a/test/spec/validations/suspicious_name.js b/test/spec/validations/suspicious_name.js index 6acb69390..532f5a542 100644 --- a/test/spec/validations/suspicious_name.js +++ b/test/spec/validations/suspicious_name.js @@ -1,3 +1,5 @@ +import { setTimeout } from 'node:timers/promises'; + describe('iD.validations.suspicious_name', function () { var context; @@ -69,123 +71,103 @@ describe('iD.validations.suspicious_name', function () { return issues; } - it('has no errors on init', function(done) { + it('has no errors on init', async () => { var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores way with no tags', function(done) { + it('ignores way with no tags', async () => { createWay({}); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores feature with no name', function(done) { + it('ignores feature with no name', async () => { createWay({ shop: 'supermarket' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores feature with a specific name', function(done) { + it('ignores feature with a specific name', async () => { createWay({ shop: 'supermarket', name: 'Lou\'s' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores feature with a specific name that includes a generic name', function(done) { + it('ignores feature with a specific name that includes a generic name', async () => { createWay({ shop: 'supermarket', name: 'Lou\'s Store' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('ignores feature matching excludeNamed pattern in name-suggestion-index', function(done) { + it('ignores feature matching excludeNamed pattern in name-suggestion-index', async () => { createWay({ shop: 'supermarket', name: 'famiglia cooperativa' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(0); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(0); }); - it('flags feature matching a excludeGeneric pattern in name-suggestion-index', function(done) { + it('flags feature matching a excludeGeneric pattern in name-suggestion-index', async () => { createWay({ shop: 'supermarket', name: 'super mercado' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('suspicious_name'); - expect(issue.subtype).to.eql('generic_name'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('suspicious_name'); + expect(issue.subtype).to.eql('generic_name'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature matching a global exclude pattern in name-suggestion-index', function(done) { + it('flags feature matching a global exclude pattern in name-suggestion-index', async () => { createWay({ shop: 'supermarket', name: 'store' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('suspicious_name'); - expect(issue.subtype).to.eql('generic_name'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('suspicious_name'); + expect(issue.subtype).to.eql('generic_name'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature with a name that is just a defining tag key', function(done) { + it('flags feature with a name that is just a defining tag key', async () => { createWay({ amenity: 'drinking_water', name: 'Amenity' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('suspicious_name'); - expect(issue.subtype).to.eql('generic_name'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('suspicious_name'); + expect(issue.subtype).to.eql('generic_name'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature with a name that is just a defining tag value', function(done) { + it('flags feature with a name that is just a defining tag value', async () => { createWay({ shop: 'red_bicycle_emporium', name: 'Red Bicycle Emporium' }); var validator = iD.validationSuspiciousName(context); - window.setTimeout(function() { // async, so data will be available - var issues = validate(validator); - expect(issues).to.have.lengthOf(1); - var issue = issues[0]; - expect(issue.type).to.eql('suspicious_name'); - expect(issue.subtype).to.eql('generic_name'); - expect(issue.entityIds).to.have.lengthOf(1); - expect(issue.entityIds[0]).to.eql('w-1'); - done(); - }, 20); + await setTimeout(20); + var issues = validate(validator); + expect(issues).to.have.lengthOf(1); + var issue = issues[0]; + expect(issue.type).to.eql('suspicious_name'); + expect(issue.subtype).to.eql('generic_name'); + expect(issue.entityIds).to.have.lengthOf(1); + expect(issue.entityIds[0]).to.eql('w-1'); }); it('flags feature with a name that matches the preset name', async () => { diff --git a/test/spec_helpers.mts b/test/spec_helpers.mts index f56e14037..f312c2f71 100644 --- a/test/spec_helpers.mts +++ b/test/spec_helpers.mts @@ -34,33 +34,6 @@ UIEvent.prototype.initUIEvent = function (...args) { return initUIEvent.apply(this, args); }; -// 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?: (err?: any) => void) => void | Promise) => { - _it(msg, () => { - if (fn.length) { - // there is a done callback -> return a promise instead - return new Promise((resolve, reject) => fn(err => { - if (err) { - reject(err); - } else { - resolve(); - } - })); - } else { - // no done callback -> normal behaviour - return fn(); - } - }); - }, - { todo: _it.todo, skip: _it.skip, only: _it.only, each: _it.each }, - ), -); - // must be imported after global envs are defined await import('../modules/id.js'); const iD = global.iD;