Initiate async wikidata lookup onchange only (not onblur), add tests

This commit is contained in:
Bryan Housel
2016-05-26 16:16:37 -04:00
parent 0135fbe4a6
commit db6234b2fc
2 changed files with 145 additions and 39 deletions
+34 -20
View File
@@ -55,7 +55,7 @@ iD.ui.preset.wikipedia = function(field, context) {
title
.call(titlecombo)
.on('blur', change)
.on('blur', blur)
.on('change', change);
link = selection.selectAll('a.wiki-link')
@@ -82,10 +82,14 @@ iD.ui.preset.wikipedia = function(field, context) {
function changeLang() {
lang.value(language()[1]);
change();
change(true);
}
function change() {
function blur() {
change(true);
}
function change(skipWikidata) {
var value = title.value(),
m = value.match(/https?:\/\/([-a-z]+)\.wikipedia\.org\/(?:wiki|\1-[-a-z]+)\/([^#]+)(?:#(.+))?/),
l = m && _.find(iD.data.wikipedia, function(d) { return m[1] === d[2]; }),
@@ -110,33 +114,43 @@ iD.ui.preset.wikipedia = function(field, context) {
}
syncTags.wikipedia = value ? language()[2] + ':' + value : undefined;
syncTags.wikidata = undefined;
if (!skipWikidata) {
syncTags.wikidata = undefined;
}
dispatch.change(syncTags);
if (skipWikidata || !value || !language()[2]) return;
// attempt asynchronous update of wikidata tag..
if (value && language()[2]) {
var initGraph = context.graph();
wikidata.itemsByTitle(language()[2], value, function (title, data) {
var qids = data && Object.keys(data),
currGraph = context.graph();
var initEntityId = entity.id,
initWikipedia = context.entity(initEntityId).tags.wikipedia;
// only do this if graph is unchanged..
if (initGraph !== currGraph) return;
wikidata.itemsByTitle(language()[2], value, function (title, data) {
// 1. most recent change was a tag change
var annotation = t('operations.change_tags.annotation'),
currAnnotation = context.history().undoAnnotation();
if (currAnnotation !== annotation) return;
var currEntity = context.entity(entity.id),
currTags = _.clone(currEntity.tags);
// 2. same entity exists and still selected
var selectedIds = context.selectedIDs(),
currEntityId = selectedIds.length > 0 && selectedIds[0];
if (currEntityId !== initEntityId) return;
currTags.wikidata = qids && _.find(qids, function (id) {
return id.match(/^Q\d+$/);
});
// 3. wikipedia value has not changed
var currTags = _.clone(context.entity(currEntityId).tags),
qids = data && Object.keys(data);
if (initWikipedia !== currTags.wikipedia) return;
var annotation = t('operations.change_tags.annotation');
context.overwrite(iD.actions.ChangeTags(currEntity.id, currTags), annotation);
dispatch.change(currTags);
// ok to coalesce the update of wikidata tag into the previous tag change
currTags.wikidata = qids && _.find(qids, function (id) {
return id.match(/^Q\d+$/);
});
}
context.overwrite(iD.actions.ChangeTags(currEntityId, currTags), annotation);
dispatch.change(currTags);
});
}
wiki.tags = function(tags) {
+111 -19
View File
@@ -1,16 +1,46 @@
describe('iD.ui.preset.wikipedia', function() {
var entity, context, selection, field, entity;
var entity, context, selection, field, wikiDelay, selectedId;
function wikidataStub() {
wikidataStub.itemsByTitle = function(lang, title, callback) {
var data = {Q216353: {id: 'Q216353'}};
if (wikiDelay) {
window.setTimeout(function () { callback(title, data); }, wikiDelay);
}
else {
callback(title, data);
}
}
return wikidataStub;
}
function changeTags(changed) {
var annotation = t('operations.change_tags.annotation');
var tags = _.extend({}, entity.tags, changed);
context.perform(iD.actions.ChangeTags(entity.id, tags), annotation);
}
beforeEach(function() {
entity = iD.Node({id: 'n12345'});
selectedId = entity.id;
context = iD();
context.history().merge([entity]);
selection = d3.select(document.createElement('div'));
field = context.presets(iD.data.presets).presets().field('wikipedia');
wikiDelay = 0;
sinon.stub(iD.services, 'wikidata', wikidataStub);
sinon.stub(context, 'selectedIDs', function() { return [selectedId]; });
});
afterEach(function() {
iD.services.wikidata.restore();
context.selectedIDs.restore();
});
it('recognizes lang:title format', function() {
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
var wikipedia = iD.ui.preset.wikipedia(field, context);
selection.call(wikipedia);
wikipedia.tags({wikipedia: 'en:Title'});
expect(selection.selectAll('.wiki-lang').value()).to.equal('English');
@@ -18,46 +48,108 @@ describe('iD.ui.preset.wikipedia', function() {
expect(selection.selectAll('.wiki-link').attr('href')).to.equal('https://en.wikipedia.org/wiki/Title');
});
it('sets a new value', function() {
it('sets language, value, wikidata', function() {
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
wikipedia.on('change', changeTags);
selection.call(wikipedia);
wikipedia.on('change', function(tags) {
expect(tags).to.eql({wikipedia: undefined, wikidata: undefined});
});
var spy = sinon.spy();
wikipedia.on('change.spy', spy);
selection.selectAll('.wiki-lang').value('Deutsch');
happen.once(selection.selectAll('.wiki-lang').node(), {type: 'change'});
wikipedia.on('change', function(tags) {
expect(tags).to.satisfy(function (tags) {
return tags.wikipedia === 'de:Title' || 'wikidata' in tags;
});
});
happen.once(selection.selectAll('.wiki-lang').node(), { type: 'change' });
happen.once(selection.selectAll('.wiki-lang').node(), { type: 'blur' });
expect(spy.callCount).to.equal(2);
expect(spy.firstCall).to.have.been.calledWith({ wikipedia: undefined }); // on change
expect(spy.secondCall).to.have.been.calledWith({ wikipedia: undefined }); // on blur
spy = sinon.spy();
wikipedia.on('change.spy', spy);
selection.selectAll('.wiki-title').value('Title');
happen.once(selection.selectAll('.wiki-title').node(), {type: 'change'});
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
expect(spy.callCount).to.equal(3);
expect(spy.firstCall).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: undefined }); // on change
expect(spy.secondCall).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: 'Q216353' }); // wikidata async
expect(spy.thirdCall).to.have.been.calledWith({ wikipedia: 'de:Title' }); // on blur
});
it('recognizes pasted URLs', function() {
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
wikipedia.on('change', changeTags);
selection.call(wikipedia);
selection.selectAll('.wiki-title').value('http://de.wikipedia.org/wiki/Title');
happen.once(selection.selectAll('.wiki-title').node(), {type: 'change'});
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
expect(selection.selectAll('.wiki-lang').value()).to.equal('Deutsch');
expect(selection.selectAll('.wiki-title').value()).to.equal('Title');
});
it('preserves existing language', function() {
selection.call(iD.ui.preset.wikipedia(field, context).entity(entity));
selection.call(iD.ui.preset.wikipedia(field, context));
selection.selectAll('.wiki-lang').value('Deutsch');
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
var wikipedia = iD.ui.preset.wikipedia(field, context);
selection.call(wikipedia);
wikipedia.tags({});
expect(selection.selectAll('.wiki-lang').value()).to.equal('Deutsch');
});
it('does not set delayed wikidata tag if wikipedia field has changed', function(done) {
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
wikipedia.on('change', changeTags);
selection.call(wikipedia);
wikiDelay = 20;
var spy = sinon.spy();
wikipedia.on('change.spy', spy);
selection.selectAll('.wiki-lang').value('Deutsch');
selection.selectAll('.wiki-title').value('Skip');
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
window.setTimeout(function() {
selection.selectAll('.wiki-title').value('Title');
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
}, 10);
window.setTimeout(function() {
expect(spy.callCount).to.equal(5);
expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Skip', wikidata: undefined }); // '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', wikidata: undefined }); // 'Title' on change +10ms
expect(spy.getCall(3)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur +10ms
// skip delayed wikidata for 'Skip' // 'Skip' wikidata +20ms
expect(spy.getCall(4)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: 'Q216353' }); // 'Title' wikidata +40ms
done();
}, 50);
});
it('does not set delayed wikidata tag if selected entity has changed', function(done) {
var wikipedia = iD.ui.preset.wikipedia(field, context).entity(entity);
wikipedia.on('change', changeTags);
selection.call(wikipedia);
wikiDelay = 20;
var spy = sinon.spy();
wikipedia.on('change.spy', spy);
selection.selectAll('.wiki-lang').value('Deutsch');
selection.selectAll('.wiki-title').value('Title');
happen.once(selection.selectAll('.wiki-title').node(), { type: 'change' });
happen.once(selection.selectAll('.wiki-title').node(), { type: 'blur' });
window.setTimeout(function() {
selectedId = 'w-123'; // user clicked on something else..
}, 10);
window.setTimeout(function() {
expect(spy.callCount).to.equal(2);
expect(spy.getCall(0)).to.have.been.calledWith({ wikipedia: 'de:Title', wikidata: undefined }); // 'Title' on change
expect(spy.getCall(1)).to.have.been.calledWith({ wikipedia: 'de:Title' }); // 'Title' on blur
// wikidata tag not changed because another entity is now selected
done();
}, 50);
});
});