Merge remote branch 'upstream/master'

This commit is contained in:
Dr Ian
2013-02-03 07:40:07 +01:00
23 changed files with 480 additions and 335 deletions
+1
View File
@@ -121,6 +121,7 @@
<script src='js/id/operations/reverse.js'></script>
<script src='js/id/operations/split.js'></script>
<script src='js/id/graph/difference.js'></script>
<script src='js/id/graph/entity.js'></script>
<script src='js/id/graph/graph.js'></script>
<script src='js/id/graph/history.js'></script>
+16 -14
View File
@@ -1,12 +1,11 @@
iD.actions.Circularize = function(wayId, map) {
iD.actions.Circularize = function(wayId, projection) {
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
tags = {}, key, role;
nodes = _.uniq(graph.childNodes(way));
var points = nodes.map(function(n) {
return map.projection(n.loc);
return projection(n.loc);
}),
centroid = d3.geom.polygon(points).centroid(),
radius = d3.median(points, function(p) {
@@ -15,14 +14,12 @@ iD.actions.Circularize = function(wayId, map) {
circular_nodes = [];
for (var i = 0; i < 12; i++) {
circular_nodes.push(iD.Node({ loc: map.projection.invert([
circular_nodes.push(iD.Node({ loc: projection.invert([
centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius,
centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius])
}));
}
circular_nodes.push(circular_nodes[0]);
for (i = 0; i < nodes.length; i++) {
if (graph.parentWays(nodes[i]).length > 1) {
var closest, closest_dist = Infinity, dist;
@@ -34,10 +31,6 @@ iD.actions.Circularize = function(wayId, map) {
}
}
circular_nodes.splice(closest, 1, nodes[i]);
if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]);
else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]);
} else {
graph = graph.remove(nodes[i]);
}
}
@@ -45,9 +38,18 @@ iD.actions.Circularize = function(wayId, map) {
graph = graph.replace(circular_nodes[i]);
}
return graph.replace(way.update({
nodes: _.pluck(circular_nodes, 'id')
}));
var ids = _.pluck(circular_nodes, 'id'),
difference = _.difference(_.uniq(way.nodes), ids);
ids.push(ids[0]);
graph = graph.replace(way.update({nodes: ids}));
for (i = 0; i < difference.length; i++) {
graph = iD.actions.DeleteNode(difference[i])(graph);
}
return graph;
};
action.enabled = function(graph) {
+6 -3
View File
@@ -16,10 +16,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
function move(datum) {
var loc = context.map().mouseCoordinates();
if (datum.type === 'node' || datum.type === 'midpoint') {
if (datum.type === 'node') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
} else if (datum.type === 'midpoint' || datum.type === 'way') {
var way = datum.type === 'way' ?
datum :
baseGraph.entity(datum.ways[0].id);
loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc;
}
context.replace(iD.actions.MoveNode(nodeId, loc));
+1 -1
View File
@@ -45,7 +45,7 @@ iD.behavior.Hash = function(context) {
context.map().on('drawn.hash', function() {
if (!context.entity(id)) return;
selectoff();
context.enter(iD.modes.Select([id]));
context.enter(iD.modes.Select(context, [id]));
});
context.on('enter.hash', function() {
+121
View File
@@ -0,0 +1,121 @@
/*
iD.Difference represents the difference between two graphs.
It knows how to calculate the set of entities that were
created, modified, or deleted, and also contains the logic
for recursively extending a difference to the complete set
of entities that will require a redraw, taking into account
child and parent relationships.
*/
iD.Difference = function (base, head) {
var changes = {}, length = 0;
_.each(head.entities, function(h, id) {
var b = base.entities[id];
if (h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
_.each(base.entities, function(b, id) {
var h = head.entities[id];
if (!changes[id] && h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
var difference = {};
difference.length = function () {
return length;
};
difference.changes = function() {
return changes;
};
difference.extantIDs = function() {
var result = [];
_.each(changes, function(change, id) {
if (change.head) result.push(id);
});
return result;
};
difference.modified = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && change.head) result.push(change.head);
});
return result;
};
difference.created = function() {
var result = [];
_.each(changes, function(change) {
if (!change.base && change.head) result.push(change.head);
});
return result;
};
difference.deleted = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && !change.head) result.push(change.base);
});
return result;
};
difference.complete = function(extent) {
var result = {}, id, change;
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (parent.id in result)
continue;
result[parent.id] = parent;
addParents(head.parentRelations(parent));
}
}
for (id in changes) {
change = changes[id];
var h = change.head,
b = change.base,
entity = h || b;
if (extent && !entity.intersects(extent, h ? head : base))
continue;
result[id] = h;
if (entity.type === 'way') {
var nh = h ? h.nodes : [],
nb = b ? b.nodes : [],
diff;
diff = _.difference(nh, nb);
for (var i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
diff = _.difference(nb, nh);
for (var i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
}
addParents(head.parentWays(entity));
addParents(head.parentRelations(entity));
}
return result;
};
return difference;
};
+1 -10
View File
@@ -43,7 +43,6 @@ iD.Entity.prototype = {
if (!this.id && this.type) {
this.id = iD.Entity.id(this.type);
this._updated = true;
}
if (iD.debug) {
@@ -63,7 +62,7 @@ iD.Entity.prototype = {
},
update: function(attrs) {
return iD.Entity(this, attrs, {_updated: true});
return iD.Entity(this, attrs);
},
mergeTags: function(tags) {
@@ -80,14 +79,6 @@ iD.Entity.prototype = {
return this.update({tags: merged});
},
created: function() {
return this._updated && this.osmId().charAt(0) === '-';
},
modified: function() {
return this._updated && this.osmId().charAt(0) !== '-';
},
intersects: function(extent, resolver) {
return this.extent(resolver).intersects(extent);
},
-60
View File
@@ -232,65 +232,5 @@ iD.Graph.prototype = {
}
}
return items;
},
difference: function (graph) {
function diff(a, b) {
var result = [],
keys = Object.keys(a.entities),
entity, oldentity, id, i;
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = a.entities[id];
oldentity = b.entities[id];
if (entity !== oldentity) {
// maybe adding affected children better belongs in renderer/map.js?
if (entity && entity.type === 'way' &&
oldentity && oldentity.type === 'way') {
result = result
.concat(_.difference(entity.nodes, oldentity.nodes))
.concat(_.difference(oldentity.nodes, entity.nodes));
} else if (entity && entity.type === 'way') {
result = result.concat(entity.nodes);
} else if (oldentity && oldentity.type === 'way') {
result = result.concat(oldentity.nodes);
}
result.push(id);
}
}
return result;
}
return _.unique(diff(this, graph).concat(diff(graph, this)).sort());
},
modified: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && base[id]) result.push(id);
});
return result;
},
created: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && !base[id]) result.push(id);
});
return result;
},
deleted: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (!entity && base[id]) result.push(id);
});
return result;
}
};
+23 -23
View File
@@ -21,7 +21,9 @@ iD.History = function() {
}
function change(previous) {
dispatch.change(history.graph().difference(previous));
var difference = iD.Difference(previous, history.graph());
dispatch.change(difference);
return difference;
}
var history = {
@@ -33,6 +35,8 @@ iD.History = function() {
for (var i = 0; i < stack.length; i++) {
stack[i].graph.rebase(entities);
}
dispatch.change();
},
perform: function () {
@@ -42,7 +46,7 @@ iD.History = function() {
stack.push(perform(arguments));
index++;
change(previous);
return change(previous);
},
replace: function () {
@@ -51,7 +55,7 @@ iD.History = function() {
// assert(index == stack.length - 1)
stack[index] = perform(arguments);
change(previous);
return change(previous);
},
pop: function () {
@@ -60,7 +64,7 @@ iD.History = function() {
if (index > 0) {
index--;
stack.pop();
change(previous);
return change(previous);
}
},
@@ -80,7 +84,7 @@ iD.History = function() {
}
dispatch.undone();
change(previous);
return change(previous);
},
redo: function () {
@@ -92,7 +96,7 @@ iD.History = function() {
}
dispatch.redone();
change(previous);
return change(previous);
},
undoAnnotation: function () {
@@ -111,31 +115,27 @@ iD.History = function() {
}
},
changes: function () {
var initial = stack[0].graph,
current = stack[index].graph;
difference: function () {
var base = stack[0].graph,
head = stack[index].graph;
return iD.Difference(base, head);
},
changes: function () {
var difference = history.difference();
return {
modified: current.modified().map(function (id) {
return current.entity(id);
}),
created: current.created().map(function (id) {
return current.entity(id);
}),
deleted: current.deleted().map(function (id) {
return initial.entity(id);
})
};
modified: difference.modified(),
created: difference.created(),
deleted: difference.deleted()
}
},
hasChanges: function() {
return !!this.numChanges();
return this.difference().length() > 0;
},
numChanges: function() {
return d3.sum(d3.values(this.changes()).map(function(c) {
return c.length;
}));
return this.difference().length();
},
imagery_used: function(source) {
+4
View File
@@ -16,6 +16,10 @@ window.iD = function () {
// the connection requires .storage() to be available on calling.
var connection = iD.Connection(context);
connection.on('load.context', function (err, result) {
history.merge(result);
});
/* Straight accessors. Avoid using these if you can. */
context.ui = function() { return ui; };
context.connection = function() { return connection; };
+1 -1
View File
@@ -1,6 +1,6 @@
iD.operations.Circularize = function(selection, context) {
var entityId = selection[0],
action = iD.actions.Circularize(entityId, context.map());
action = iD.actions.Circularize(entityId, context.projection);
var operation = function() {
var annotation = t('operations.circularize.annotation.' + context.geometry(entityId));
+3 -3
View File
@@ -2,9 +2,9 @@ iD.operations.Merge = function(selection, context) {
var action = iD.actions.Join(selection[0], selection[1]);
var operation = function() {
context.perform(
action,
t('operations.merge.annotation', {n: selection.length}));
var annotation = t('operations.merge.annotation', {n: selection.length}),
difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
+3 -1
View File
@@ -3,7 +3,9 @@ iD.operations.Split = function(selection, context) {
action = iD.actions.Split(entityId);
var operation = function() {
context.perform(action, t('operations.split.annotation'));
var annotation = t('operations.split.annotation'),
difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
+4 -37
View File
@@ -26,9 +26,6 @@ iD.Map = function(context) {
surface, tilegroup;
function map(selection) {
context.connection()
.on('load.tile', connectionLoad);
context.history()
.on('change.map', redraw);
@@ -64,44 +61,19 @@ iD.Map = function(context) {
extent = map.extent(),
graph = context.graph();
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (only[parent.id] === undefined) {
only[parent.id] = parent;
addParents(graph.parentRelations(parent));
}
}
}
if (!difference) {
all = graph.intersects(extent);
filter = d3.functor(true);
} else {
var only = {};
for (var j = 0; j < difference.length; j++) {
var id = difference[j],
entity = graph.entity(id);
// Even if the entity is false (deleted), it needs to be
// removed from the surface
only[id] = entity;
if (entity && entity.intersects(extent, graph)) {
addParents(graph.parentWays(only[id]));
addParents(graph.parentRelations(only[id]));
}
}
all = _.compact(_.values(only));
var complete = difference.complete(extent);
all = _.compact(_.values(complete));
filter = function(d) {
if (d.type === 'midpoint') {
for (var i = 0; i < d.ways.length; i++) {
if (d.ways[i].id in only) return true;
if (d.ways[i].id in complete) return true;
}
} else {
return d.id in only;
return d.id in complete;
}
};
}
@@ -125,11 +97,6 @@ iD.Map = function(context) {
surface.selectAll('.layer *').remove();
}
function connectionLoad(err, result) {
context.history().merge(result);
redraw(Object.keys(result));
}
function zoomPan() {
if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
if (!dblclickEnabled) {
+19 -21
View File
@@ -15,31 +15,29 @@ iD.validate = function(changes, graph) {
if (tags.building && tags.building === 'yes') return 'building=yes';
}
if (changes.created.length) {
for (var i = 0; i < changes.created.length; i++) {
change = changes.created[i];
for (var i = 0; i < changes.created.length; i++) {
change = changes.created[i];
if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) {
warnings.push({
message: t('validations.untagged_point'),
entity: change
});
}
if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) {
warnings.push({
message: t('validations.untagged_point'),
entity: change
});
}
if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_line'), entity: change });
}
if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_line'), entity: change });
}
if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_area'), entity: change });
}
if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) {
warnings.push({ message: t('validations.untagged_area'), entity: change });
}
if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) {
warnings.push({
message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}),
entity: change
});
}
if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) {
warnings.push({
message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}),
entity: change
});
}
}
+2
View File
@@ -115,6 +115,7 @@
<script src='../js/id/operations/reverse.js'></script>
<script src='../js/id/operations/split.js'></script>
<script src='../js/id/graph/difference.js'></script>
<script src='../js/id/graph/entity.js'></script>
<script src='../js/id/graph/graph.js'></script>
<script src='../js/id/graph/history.js'></script>
@@ -164,6 +165,7 @@
<script src="spec/graph/way.js"></script>
<script src="spec/graph/relation.js"></script>
<script src="spec/graph/history.js"></script>
<script src="spec/graph/difference.js"></script>
<script src="spec/renderer/background.js"></script>
<script src="spec/renderer/map.js"></script>
+1
View File
@@ -55,6 +55,7 @@
<script src="spec/graph/way.js"></script>
<script src="spec/graph/relation.js"></script>
<script src="spec/graph/history.js"></script>
<script src="spec/graph/difference.js"></script>
<script src="spec/renderer/background.js"></script>
<script src="spec/renderer/map.js"></script>
+229
View File
@@ -0,0 +1,229 @@
describe("iD.Difference", function () {
describe("#changes", function () {
it("includes created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it("includes undone created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it("includes modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.update(),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: n1, head: n2}});
});
it("includes undone modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.update(),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: n2, head: n1}});
});
it("includes deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it("includes undone deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(head, base);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it("doesn't include created entities that were subsequently deleted", function () {
var node = iD.Node(),
base = iD.Graph(),
head = base.replace(node).remove(node),
diff = iD.Difference(base, head);
expect(diff.changes()).to.eql({});
});
});
describe("#extantIDs", function () {
it("includes the ids of created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it("includes the ids of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it("omits the ids of deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.extantIDs()).to.eql([]);
});
});
describe("#created", function () {
it("returns an array of created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.created()).to.eql([node]);
});
});
describe("#modified", function () {
it("returns an array of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.modified()).to.eql([n2]);
});
});
describe("#deleted", function () {
it("returns an array of deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.deleted()).to.eql([node]);
});
});
describe("#complete", function () {
it("includes created entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph(),
head = base.replace(node),
diff = iD.Difference(base, head);
expect(diff.complete()['n']).to.equal(node);
});
it("includes modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.Graph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['n']).to.equal(n2);
});
it("includes deleted entities", function () {
var node = iD.Node({id: 'n'}),
base = iD.Graph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
expect(diff.complete()).to.eql({n: undefined});
});
it("includes nodes added to a way", function () {
var n1 = iD.Node({id: 'n1'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w', nodes: ['n1']}),
w2 = w1.addNode('n2'),
base = iD.Graph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
expect(diff.complete()['n2']).to.equal(n2);
});
it("includes nodes removed from a way", function () {
var n1 = iD.Node({id: 'n1'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w', nodes: ['n1', 'n2']}),
w2 = w1.removeNode('n2'),
base = iD.Graph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
expect(diff.complete()['n2']).to.equal(n2);
});
it("includes parent ways of modified nodes", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.Way({id: 'w', nodes: ['n']}),
base = iD.Graph([n1, way]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['w']).to.equal(way);
});
it("includes parent relations of modified entities", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
rel = iD.Relation({id: 'r', members: [{id: 'n'}]}),
base = iD.Graph([n1, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r']).to.equal(rel);
});
it("includes parent relations of modified entities, recursively", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}),
rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.Graph([n1, rel1, rel2]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r2']).to.equal(rel2);
});
it("includes parent relations of parent ways of modified nodes", function () {
var n1 = iD.Node({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.Way({id: 'w', nodes: ['n']}),
rel = iD.Relation({id: 'r', members: [{id: 'w'}]}),
base = iD.Graph([n1, way, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
expect(diff.complete()['r']).to.equal(rel);
});
it("copes with recursive relations", function () {
var node = iD.Node({id: 'n'}),
rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}),
rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.Graph([node, rel1, rel2]),
head = base.replace(node.move([1, 2])),
diff = iD.Difference(base, head);
expect(diff.complete()).to.be.ok;
});
it("limits changes to those within a given extent");
});
});
-42
View File
@@ -52,12 +52,6 @@ describe('iD.Entity', function () {
expect(e.id).to.equal('w1');
});
it("tags the entity as updated", function () {
var tags = {foo: 'bar'},
e = iD.Entity().update({tags: tags});
expect(e._updated).to.to.be.true;
});
it("doesn't modify the input", function () {
var attrs = {tags: {foo: 'bar'}},
e = iD.Entity().update(attrs);
@@ -104,42 +98,6 @@ describe('iD.Entity', function () {
});
});
describe("#created", function () {
it("returns falsy by default", function () {
expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok;
});
it("returns falsy for an unmodified Entity", function () {
expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok;
});
it("returns falsy for a modified Entity with positive ID", function () {
expect(iD.Entity({id: 'w1234'}).update({}).created()).not.to.be.ok;
});
it("returns truthy for a modified Entity with negative ID", function () {
expect(iD.Entity({id: 'w-1234'}).update({}).created()).to.be.ok;
});
});
describe("#modified", function () {
it("returns falsy by default", function () {
expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok;
});
it("returns falsy for an unmodified Entity", function () {
expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok;
});
it("returns truthy for a modified Entity with positive ID", function () {
expect(iD.Entity({id: 'w1234'}).update({}).modified()).to.be.ok;
});
it("returns falsy for a modified Entity with negative ID", function () {
expect(iD.Entity({id: 'w-1234'}).update({}).modified()).not.to.be.ok;
});
});
describe("#intersects", function () {
it("returns true for a way with a node within the given extent", function () {
var node = iD.Node({loc: [0, 0]}),
-82
View File
@@ -333,86 +333,4 @@ describe('iD.Graph', function() {
expect(graph.childNodes(way)).to.eql([node]);
});
});
describe("#difference", function () {
it("returns an Array of ids of changed entities", function () {
var initial = iD.Node({id: "n1"}),
updated = initial.update({}),
created = iD.Node(),
deleted = iD.Node({id: 'n2'}),
graph1 = iD.Graph([initial, deleted]),
graph2 = graph1.replace(updated).replace(created).remove(deleted);
expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]);
});
it("includes created entities, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph(),
graph2 = graph1.replace(node);
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes entities changed from base, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph(node),
graph2 = graph1.replace(node.update());
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes already changed entities that were updated, and reverse", function () {
var node = iD.Node(),
graph1 = iD.Graph().replace(node),
graph2 = graph1.replace(node.update());
expect(graph2.difference(graph1)).to.eql([node.id]);
expect(graph1.difference(graph2)).to.eql([node.id]);
});
it("includes affected child nodes", function () {
var n = iD.Node({id: 'n'}),
n2 = iD.Node({id: 'n2'}),
w1 = iD.Way({id: 'w1', nodes: ['n']}),
w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}),
graph1 = iD.Graph([n, n2, w1]),
graph2 = graph1.replace(w1_);
expect(graph2.difference(graph1)).to.eql(['n2', 'w1']);
expect(graph1.difference(graph2)).to.eql(['n2', 'w1']);
});
});
describe("#modified", function () {
it("returns an Array of ids of modified entities", function () {
var node = iD.Node({id: 'n1'}),
node_ = iD.Node({id: 'n1'}),
graph = iD.Graph([node]).replace(node_);
expect(graph.modified()).to.eql([node.id]);
});
});
describe("#created", function () {
it("returns an Array of ids of created entities", function () {
var node1 = iD.Node({id: 'n-1'}),
node2 = iD.Node({id: 'n2'}),
graph = iD.Graph([node2]).replace(node1);
expect(graph.created()).to.eql([node1.id]);
});
});
describe("#deleted", function () {
it("returns an Array of ids of deleted entities", function () {
var node1 = iD.Node({id: "n1"}),
node2 = iD.Node(),
graph = iD.Graph([node1, node2]).remove(node1);
expect(graph.deleted()).to.eql([node1.id]);
});
it("doesn't include created entities that were subsequently deleted", function () {
var node = iD.Node(),
graph = iD.Graph().replace(node).remove(node);
expect(graph.deleted()).to.eql([]);
});
});
});
+45 -10
View File
@@ -13,7 +13,25 @@ describe("iD.History", function () {
});
});
describe("#merge", function () {
it("merges the entities into all graph versions", function () {
var n = iD.Node({id: 'n'});
history.merge({n: n});
expect(history.graph().entity('n')).to.equal(n);
});
it("emits a change event", function () {
history.on('change', spy);
history.merge({});
expect(spy).to.have.been.called;
});
});
describe("#perform", function () {
it("returns a difference", function () {
expect(history.perform(action).changes()).to.eql({});
});
it("updates the graph", function () {
var node = iD.Node();
history.perform(function (graph) { return graph.replace(node); });
@@ -27,8 +45,8 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.on('change', spy);
history.perform(action);
expect(spy).to.have.been.calledWith([]);
var difference = history.perform(action);
expect(spy).to.have.been.calledWith(difference);
});
it("performs multiple actions", function () {
@@ -42,6 +60,10 @@ describe("iD.History", function () {
});
describe("#replace", function () {
it("returns a difference", function () {
expect(history.replace(action).changes()).to.eql({});
});
it("updates the graph", function () {
var node = iD.Node();
history.replace(function (graph) { return graph.replace(node); });
@@ -56,8 +78,8 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.on('change', spy);
history.replace(action);
expect(spy).to.have.been.calledWith([]);
var difference = history.replace(action);
expect(spy).to.have.been.calledWith(difference);
});
it("performs multiple actions", function () {
@@ -71,6 +93,11 @@ describe("iD.History", function () {
});
describe("#pop", function () {
it("returns a difference", function () {
history.perform(action, "annotation");
expect(history.pop().changes()).to.eql({});
});
it("updates the graph", function () {
history.perform(action, "annotation");
history.pop();
@@ -86,12 +113,16 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
history.pop();
expect(spy).to.have.been.calledWith([]);
var difference = history.pop();
expect(spy).to.have.been.calledWith(difference);
});
});
describe("#undo", function () {
it("returns a difference", function () {
expect(history.undo().changes()).to.eql({});
});
it("pops the undo stack", function () {
history.perform(action, "annotation");
history.undo();
@@ -121,12 +152,16 @@ describe("iD.History", function () {
it("emits a change event", function () {
history.perform(action);
history.on('change', spy);
history.undo();
expect(spy).to.have.been.calledWith([]);
var difference = history.undo();
expect(spy).to.have.been.calledWith(difference);
});
});
describe("#redo", function () {
it("returns a difference", function () {
expect(history.redo().changes()).to.eql({});
});
it("emits an redone event", function () {
history.perform(action);
history.undo();
@@ -139,8 +174,8 @@ describe("iD.History", function () {
history.perform(action);
history.undo();
history.on('change', spy);
history.redo();
expect(spy).to.have.been.calledWith([]);
var difference = history.redo();
expect(spy).to.have.been.calledWith(difference);
});
});
-9
View File
@@ -4,15 +4,6 @@ describe('iD.Node', function () {
expect(iD.Node().type).to.equal("node");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Node().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok;
expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok;
});
it("defaults tags to an empty object", function () {
expect(iD.Node().tags).to.eql({});
});
-9
View File
@@ -10,15 +10,6 @@ describe('iD.Relation', function () {
expect(iD.Relation().type).to.equal("relation");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Relation().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok;
expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok;
});
it("defaults members to an empty array", function () {
expect(iD.Relation().members).to.eql([]);
});
-9
View File
@@ -10,15 +10,6 @@ describe('iD.Way', function() {
expect(iD.Way().type).to.equal("way");
});
it("returns a created Entity if no ID is specified", function () {
expect(iD.Way().created()).to.be.ok;
});
it("returns an unmodified Entity if ID is specified", function () {
expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok;
expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok;
});
it("defaults nodes to an empty array", function () {
expect(iD.Way().nodes).to.eql([]);
});