From e981cd5dd569689fa451b56cf1dec48c2c78bf36 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 29 Apr 2019 15:31:08 -0400 Subject: [PATCH] Switch mapillary and openstreetcam tests to work async - can't reliably use sinon.spy to tell whether a thing has been called, so we listen for events instead and check server.requests() - make sure to request the next page before dispatching `loadedImages` so we can `server.respond()` to the request in the event handler if we want to - also moves `localeDateString` in the openstreetcam service from parsing code to display code, because it's very slow (we can just do this for the images we look at, instead of all images we fetch) --- modules/services/mapillary.js | 14 +-- modules/services/openstreetcam.js | 32 +++---- modules/svg/mapillary_signs.js | 2 +- test/spec/services/mapillary.js | 132 +++++++++++++--------------- test/spec/services/openstreetcam.js | 90 ++++++------------- 5 files changed, 115 insertions(+), 155 deletions(-) diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 101728703..df4f2a9ed 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -180,18 +180,18 @@ function loadNextTilePage(which, currZoom, url, tile) { cache.rtree.load(features); } - if (which === 'images' || which === 'sequences') { - dispatch.call('loadedImages'); - } else if (which === 'map_features') { - dispatch.call('loadedSigns'); - } - if (data.features.length === maxResults) { // more pages to load cache.nextPage[tile.id] = nextPage + 1; loadNextTilePage(which, currZoom, url, tile); } else { cache.nextPage[tile.id] = Infinity; // no more pages to load } + + if (which === 'images' || which === 'sequences') { + dispatch.call('loadedImages'); + } else if (which === 'map_features') { + dispatch.call('loadedSigns'); + } }) .catch(function() { cache.loaded[id] = true; @@ -326,7 +326,7 @@ export default { }, - loadSigns: function(context, projection) { + loadSigns: function(projection) { // if we are looking at signs, we'll actually need to fetch images too loadTiles('images', apibase + 'images?', projection); loadTiles('map_features', apibase + 'map_features?layers=trafficsigns&min_nbr_image_detections=1&', projection); diff --git a/modules/services/openstreetcam.js b/modules/services/openstreetcam.js index 366632047..768d41da3 100644 --- a/modules/services/openstreetcam.js +++ b/modules/services/openstreetcam.js @@ -95,15 +95,6 @@ function loadNextTilePage(which, currZoom, url, tile) { throw new Error('No Data'); } - function localeDateString(s) { - if (!s) return null; - var detected = utilDetect(); - var options = { day: 'numeric', month: 'short', year: 'numeric' }; - var d = new Date(s); - if (isNaN(d.getTime())) return null; - return d.toLocaleDateString(detected.locale, options); - } - var features = data.currentPageItems.map(function(item) { var loc = [+item.lng, +item.lat]; var d; @@ -113,7 +104,7 @@ function loadNextTilePage(which, currZoom, url, tile) { loc: loc, key: item.id, ca: +item.heading, - captured_at: localeDateString(item.shot_date || item.date_added), + captured_at: (item.shot_date || item.date_added), captured_by: item.username, imagePath: item.lth_name, sequence_id: item.sequence_id, @@ -136,16 +127,16 @@ function loadNextTilePage(which, currZoom, url, tile) { cache.rtree.load(features); - if (which === 'images') { - dispatch.call('loadedImages'); - } - if (data.currentPageItems.length === maxResults) { // more pages to load cache.nextPage[tile.id] = nextPage + 1; loadNextTilePage(which, currZoom, url, tile); } else { cache.nextPage[tile.id] = Infinity; // no more pages to load } + + if (which === 'images') { + dispatch.call('loadedImages'); + } }) .catch(function() { cache.loaded[id] = true; @@ -439,7 +430,7 @@ export default { attribution .append('span') .attr('class', 'captured_at') - .text(d.captured_at); + .text(localeDateString(d.captured_at)); attribution .append('span') @@ -453,7 +444,18 @@ export default { .attr('href', 'https://openstreetcam.org/details/' + d.sequence_id + '/' + d.sequence_index) .text('openstreetcam.org'); } + return this; + + + function localeDateString(s) { + if (!s) return null; + var detected = utilDetect(); + var options = { day: 'numeric', month: 'short', year: 'numeric' }; + var d = new Date(s); + if (isNaN(d.getTime())) return null; + return d.toLocaleDateString(detected.locale, options); + } }, diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index cffffdce7..b846fb538 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -142,7 +142,7 @@ export function svgMapillarySigns(projection, context, dispatch) { if (service && ~~context.map().zoom() >= minZoom) { editOn(); update(); - service.loadSigns(context, projection); + service.loadSigns(projection); } else { editOff(); } diff --git a/test/spec/services/mapillary.js b/test/spec/services/mapillary.js index 94bc15121..03d741d83 100644 --- a/test/spec/services/mapillary.js +++ b/test/spec/services/mapillary.js @@ -55,11 +55,13 @@ describe('iD.serviceMapillary', function() { describe('#loadImages', function() { it('fires loadedImages when images are loaded', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedImages', spy); + mapillary.on('loadedImages', function() { + expect(server.requests().length).to.eql(2); // 1 images, 1 sequences + done(); + }); + mapillary.loadImages(context.projection); - var match = /images/; var features = [{ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, @@ -67,23 +69,18 @@ describe('iD.serviceMapillary', function() { }]; var response = { type: 'FeatureCollection', features: features }; - server.respondWith('GET', match, + server.respondWith('GET', /images/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 200); }); it('does not load images around null island', function(done) { var spy = sinon.spy(); context.projection.translate([0,0]); + mapillary.on('loadedImages', spy); mapillary.loadImages(context.projection); - var match = /images/; var features = [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, @@ -91,70 +88,72 @@ describe('iD.serviceMapillary', function() { }]; var response = { type: 'FeatureCollection', features: features }; - server.respondWith('GET', match, + server.respondWith('GET', /images/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); server.respond(); window.setTimeout(function() { expect(spy).to.have.been.not.called; + expect(server.requests().length).to.eql(0); // no tile requests of any kind done(); }, 200); }); - it.skip('loads multiple pages of image results', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedImages', spy); + it('loads multiple pages of image results', function(done) { + var calls = 0; + mapillary.on('loadedImages', function() { + server.respond(); // respond to new fetches + if (++calls === 2) { + expect(server.requests().length).to.eql(3); // 2 images, 1 sequences + done(); + } + }); + mapillary.loadImages(context.projection); var features0 = []; var features1 = []; - var i; + var i, key; for (i = 0; i < 1000; i++) { + key = String(i); features0.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, - properties: { ca: 90, key: String(i) } + properties: { ca: 90, key: key } }); } for (i = 0; i < 500; i++) { + key = String(1000 + i); features1.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, - properties: { ca: 90, key: String(1000 + i) } + properties: { ca: 90, key: key } }); } - var match0 = /page=0/; var response0 = { type: 'FeatureCollection', features: features0 }; - var match1 = /page=1/; var response1 = { type: 'FeatureCollection', features: features1 }; - server.respondWith('GET', match0, + server.respondWith('GET', /\/images\?.*&page=0/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]); - server.respondWith('GET', match1, + server.respondWith('GET', /\/images\?.*&page=1/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledTwice; - done(); - }, 200); }); }); describe('#loadSigns', function() { it('fires loadedSigns when signs are loaded', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedSigns', spy); - mapillary.loadSigns(context, context.projection); + mapillary.on('loadedSigns', function() { + expect(server.requests().length).to.eql(3); // 1 images, 1 map_features, 1 image_detections + done(); + }); - var match = /map_features/; - var detections = [{ - detection_key: '0', - image_key: '0' - }]; + mapillary.loadSigns(context.projection); + + var detections = [{ detection_key: '0', image_key: '0' }]; var features = [{ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, @@ -162,27 +161,19 @@ describe('iD.serviceMapillary', function() { }]; var response = { type: 'FeatureCollection', features: features }; - server.respondWith('GET', match, + server.respondWith('GET', /map_features/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 200); }); it('does not load signs around null island', function(done) { var spy = sinon.spy(); context.projection.translate([0,0]); - mapillary.on('loadedSigns', spy); - mapillary.loadSigns(context, context.projection); - var match = /map_features/; - var detections = [{ - detection_key: '0', - image_key: '0' - }]; + mapillary.on('loadedSigns', spy); + mapillary.loadSigns(context.projection); + + var detections = [{ detection_key: '0', image_key: '0' }]; var features = [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, @@ -190,62 +181,60 @@ describe('iD.serviceMapillary', function() { }]; var response = { type: 'FeatureCollection', features: features }; - server.respondWith('GET', match, + server.respondWith('GET', /map_features/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); server.respond(); window.setTimeout(function() { expect(spy).to.have.been.not.called; + expect(server.requests().length).to.eql(0); // no tile requests of any kind done(); }, 200); }); - it.skip('loads multiple pages of signs results', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedSigns', spy); - mapillary.loadSigns(context, context.projection); + it('loads multiple pages of signs results', function(done) { + var calls = 0; + mapillary.on('loadedSigns', function() { + server.respond(); // respond to new fetches + if (++calls === 2) { + expect(server.requests().length).to.eql(4); // 2 images, 1 map_features, 1 image_detections + done(); + } + }); + + mapillary.loadSigns(context.projection); - var rects = [{ - package: 'trafficsign', - rect: [ 0.805, 0.463, 0.833, 0.502 ], - length: 4, - score: '1.27', - type: 'regulatory--maximum-speed-limit-65--us' - }]; var features0 = []; var features1 = []; - var i; + var i, key, detections; for (i = 0; i < 1000; i++) { + key = String(i); + detections = [{ detection_key: key, image_key: key }]; features0.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, - properties: { rects: rects, key: String(i) } + properties: { detections: detections, key: key, value: 'not-in-set' } }); } for (i = 0; i < 500; i++) { + key = String(1000 + i); + detections = [{ detection_key: key, image_key: key }]; features1.push({ type: 'Feature', geometry: { type: 'Point', coordinates: [10,0] }, - properties: { rects: rects, key: String(1000 + i) } + properties: { detections: detections, key: key, value: 'not-in-set' } }); } - var match0 = /page=0/; var response0 = { type: 'FeatureCollection', features: features0 }; - var match1 = /page=1/; var response1 = { type: 'FeatureCollection', features: features1 }; - server.respondWith('GET', match0, + server.respondWith('GET', /\/map_features\?.*&page=0/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]); - server.respondWith('GET', match1, + server.respondWith('GET', /\/map_features\?.*&page=1/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledTwice; - done(); - }, 200); }); }); @@ -283,6 +272,7 @@ describe('iD.serviceMapillary', function() { }); }); + describe('#signs', function() { it('returns signs in the visible map area', function() { var detections = [{ diff --git a/test/spec/services/openstreetcam.js b/test/spec/services/openstreetcam.js index f64f27142..5553c4a49 100644 --- a/test/spec/services/openstreetcam.js +++ b/test/spec/services/openstreetcam.js @@ -52,8 +52,11 @@ describe('iD.serviceOpenstreetcam', function() { describe('#loadImages', function() { it('fires loadedImages when images are loaded', function(done) { - var spy = sinon.spy(); - openstreetcam.on('loadedImages', spy); + openstreetcam.on('loadedImages', function() { + expect(server.requests().length).to.eql(1); // 1 nearby-photos + done(); + }); + openstreetcam.loadImages(context.projection); var data = { @@ -101,16 +104,12 @@ describe('iD.serviceOpenstreetcam', function() { server.respondWith('POST', /nearby-photos/, [200, { 'Content-Type': 'application/json' }, JSON.stringify(data) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledOnce; - done(); - }, 200); }); it('does not load images around null island', function(done) { var spy = sinon.spy(); context.projection.translate([0,0]); + openstreetcam.on('loadedImages', spy); openstreetcam.loadImages(context.projection); @@ -162,76 +161,45 @@ describe('iD.serviceOpenstreetcam', function() { window.setTimeout(function() { expect(spy).to.have.been.not.called; + expect(server.requests().length).to.eql(0); // no tile requests of any kind done(); }, 200); }); - it.skip('loads multiple pages of image results', function(done) { - var spy = sinon.spy(); - openstreetcam.on('loadedImages', spy); + it('loads multiple pages of image results', function(done) { + openstreetcam.on('loadedImages', function() { + expect(server.requests().length).to.eql(2); // 2 nearby-photos + done(); + }); + openstreetcam.loadImages(context.projection); - var features0 = [], - features1 = [], - i; - - for (i = 0; i < 1000; i++) { - features0.push({ - id: String(i), + var features = []; + for (var i = 0; i < 1000; i++) { + var key = String(i); + features.push({ + id: key, sequence_id: '100', - sequence_index: String(i), + sequence_index: key, lat: '10', lng: '0', - name: 'storage6\/files\/photo\/foo' + String(i) +'.jpg', - lth_name: 'storage6\/files\/photo\/lth\/foo' + String(i) +'.jpg', - th_name: 'storage6\/files\/photo\/th\/foo' + String(i) +'.jpg', - shot_date: '2017-09-24 23:58:07', - heading: '90', - username: 'test' - }); - } - for (i = 0; i < 500; i++) { - features1.push({ - id: String(i), - sequence_id: '100', - sequence_index: String(1000 + i), - lat: '10', - lng: '0', - name: 'storage6\/files\/photo\/foo' + String(1000 + i) +'.jpg', - lth_name: 'storage6\/files\/photo\/lth\/foo' + String(1000 + i) +'.jpg', - th_name: 'storage6\/files\/photo\/th\/foo' + String(1000 + i) +'.jpg', + name: 'storage6\/files\/photo\/foo' + key + '.jpg', + lth_name: 'storage6\/files\/photo\/lth\/foo' + key + '.jpg', + th_name: 'storage6\/files\/photo\/th\/foo' + key + '.jpg', shot_date: '2017-09-24 23:58:07', heading: '90', username: 'test' }); } + var response = { + status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' }, + currentPageItems: features, + totalFilteredItems: ['1000'] + }; - var response0 = { - status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' }, - currentPageItems: [features0], - totalFilteredItems: ['1000'] - }, - response1 = { - status: { apiCode: '600', httpCode: 200, httpMessage: 'Success' }, - currentPageItems: [features1], - totalFilteredItems: ['500'] - }; - - server.respondWith('POST', /nearby-photos/, function (request) { - var response; - if (request.requestBody.match(/page=1/) !== null) { - response = JSON.stringify(response0); - } else if (request.requestBody.match(/page=2/) !== null) { - response = JSON.stringify(response1); - } - request.respond(200, {'Content-Type': 'application/json'}, response); - }); + server.respondWith('POST', /nearby-photos/, + [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.calledTwice; - done(); - }, 200); }); });