diff --git a/modules/util/index.js b/modules/util/index.js index 45e1266ef..26fb02c79 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -35,6 +35,7 @@ export { utilHighlightEntities } from './util'; export { utilKeybinding } from './keybinding'; export { utilNoAuto } from './util'; export { utilObjectOmit } from './object'; +export { utilOldestID } from './util'; export { utilPrefixCSSProperty } from './util'; export { utilPrefixDOMProperty } from './util'; export { utilQsString } from './util'; diff --git a/modules/util/util.js b/modules/util/util.js index e078647e6..ec3d5d539 100644 --- a/modules/util/util.js +++ b/modules/util/util.js @@ -581,3 +581,53 @@ export function utilUnicodeCharsCount(str) { export function utilUnicodeCharsTruncated(str, limit) { return Array.from(str).slice(0, limit).join(''); } + + +// Returns the chronologically oldest ID in the list. +// Database IDs (with positive numbers) before editor ones (with negative numbers). +// Among each category, the closest number to 0 is the oldest. +// Test IDs (any string that does not conform to OSM's ID scheme) are taken last. +export function utilOldestID(ids) { + if (ids.length === 0) { + return undefined; + } + + var idPattern = /^[cnwr](-?)(\d+)$/; + var decomposeID = (id) => { + var res = { + id: id, + num: NaN + }; + + var match = id.match(idPattern); + if (match) { + res.num = parseInt(match[2], 10); + res.isNew = !!match[1]; + } + + return res; + }; + + var compareDecomposed = (left, right) => { + if (isNaN(left.num) && isNaN(right.num)) return -1; + if (isNaN(left.num)) return 1; + if (isNaN(right.num)) return -1; + if (left.isNew && !right.isNew) return 1; + if (!left.isNew && right.isNew) return -1; + return Math.sign(left.num - right.num); + }; + + var oldestIDIndex = 0; + var oldestID = decomposeID(ids[0]); + + for (var i = 1; i < ids.length; i++) { + var decomposed = decomposeID(ids[i]); + + if (compareDecomposed(oldestID, decomposed) === 1) { + oldestIDIndex = i; + oldestID = decomposed; + } + } + + return ids[oldestIDIndex]; +} diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index f140d0a70..6506928ec 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -131,6 +131,14 @@ if (typeof ArrayBuffer.isView === 'undefined') { ArrayBuffer.isView = function() { return false; }; } +// Polyfill for `Math.sign()` in PhantomJS +// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign#Polyfill +if (!Math.sign) { + Math.sign = function(x) { + return ((x > 0) - (x < 0)) || +x; + }; +} + // Add support for sinon-stubbing `fetch` API // (sinon fakeServer works only on `XMLHttpRequest`) // see https://github.com/sinonjs/nise/issues/7 diff --git a/test/spec/util/util.js b/test/spec/util/util.js index 95f8e1c18..9768bfa12 100644 --- a/test/spec/util/util.js +++ b/test/spec/util/util.js @@ -252,4 +252,25 @@ describe('iD.util', function() { expect(iD.utilDisplayName({tags: {network: 'BART', ref: 'Yellow', from: 'Antioch', to: 'Millbrae', via: 'Pittsburg/Bay Point;San Francisco International Airport', route: 'subway'}})).to.eql('BART Yellow from Antioch to Millbrae via Pittsburg/Bay Point;San Francisco International Airport'); }); }); + + describe('utilOldestID', function() { + it('returns the oldest database ID', function() { + expect(iD.utilOldestID(['w3', 'w1', 'w2'])).to.eql('w1'); + }); + it('returns the oldest editor ID', function() { + expect(iD.utilOldestID(['w-3', 'w-2', 'w-1'])).to.eql('w-1'); + }); + it('returns the oldest IDs among database and editor IDs', function() { + expect(iD.utilOldestID(['w-1', 'w1', 'w-2'])).to.eql('w1'); + }); + it('returns the oldest database ID', function() { + expect(iD.utilOldestID(['w100', 'w-1', 'a', 'w-300', 'w2'])).to.eql('w2'); + }); + it('returns the oldest editor ID if no database IDs', function() { + expect(iD.utilOldestID(['w-100', 'w-1', 'a', 'w-300', 'w-2'])).to.eql('w-1'); + }); + it('returns the first ID in the list otherwise', function() { + expect(iD.utilOldestID(['z', 'a', 'A', 'Z'])).to.eql('z'); + }); + }); });