Preserve connectivity when pasting

Fixes #2584
This commit is contained in:
John Firebaugh
2016-02-07 23:02:26 -08:00
committed by Bryan Housel
parent 4a93977f43
commit a85361545d
14 changed files with 263 additions and 310 deletions

View File

@@ -155,7 +155,7 @@
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/connect.js'></script>
<script src='js/id/actions/copy_entity.js'></script>
<script src='js/id/actions/copy_entities.js'></script>
<script src='js/id/actions/delete_member.js'></script>
<script src='js/id/actions/delete_multiple.js'></script>
<script src='js/id/actions/delete_node.js'></script>

View File

@@ -0,0 +1,21 @@
iD.actions.CopyEntities = function(ids, fromGraph) {
var copies = {};
var action = function(graph) {
ids.forEach(function(id) {
fromGraph.entity(id).copy(fromGraph, copies);
});
for (var id in copies) {
graph = graph.replace(copies[id]);
}
return graph;
};
action.copies = function() {
return copies;
};
return action;
};

View File

@@ -1,21 +0,0 @@
iD.actions.CopyEntity = function(id, fromGraph, deep) {
var newEntities = [];
var action = function(graph) {
var entity = fromGraph.entity(id);
newEntities = entity.copy(deep, fromGraph);
for (var i = 0; i < newEntities.length; i++) {
graph = graph.replace(newEntities[i]);
}
return graph;
};
action.newEntities = function() {
return newEntities;
};
return action;
};

View File

@@ -31,30 +31,21 @@ iD.behavior.Paste = function(context) {
var extent = iD.geo.Extent(),
oldIDs = context.copyIDs(),
oldGraph = context.copyGraph(),
newIDs = [],
i, j;
newIDs = [];
if (!oldIDs.length) return;
for (i = 0; i < oldIDs.length; i++) {
var oldEntity = oldGraph.entity(oldIDs[i]),
action = iD.actions.CopyEntity(oldEntity.id, oldGraph, true),
newEntities;
var action = iD.actions.CopyEntities(oldIDs, oldGraph);
context.perform(action);
var copies = action.copies();
for (var id in copies) {
var oldEntity = oldGraph.entity(id),
newEntity = copies[id];
extent._extend(oldEntity.extent(oldGraph));
context.perform(action);
// First element in `newEntities` contains the copied Entity,
// Subsequent array elements contain any descendants..
newEntities = action.newEntities();
newIDs.push(newEntities[0].id);
for (j = 0; j < newEntities.length; j++) {
var newEntity = newEntities[j],
tags = _.omit(newEntity.tags, omitTag);
context.perform(iD.actions.ChangeTags(newEntity.id, tags));
}
newIDs.push(newEntity.id);
context.perform(iD.actions.ChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag)));
}
// Put pasted objects where mouse pointer is..

View File

@@ -72,10 +72,14 @@ iD.Entity.prototype = {
return this;
},
copy: function() {
// Returns an array so that we can support deep copying ways and relations.
// The first array element will contain this.copy, followed by any descendants.
return [iD.Entity(this, {id: undefined, user: undefined, version: undefined})];
copy: function(resolver, copies) {
if (copies[this.id])
return copies[this.id];
var copy = iD.Entity(this, {id: undefined, user: undefined, version: undefined});
copies[this.id] = copy;
return copy;
},
osmId: function() {

View File

@@ -20,31 +20,19 @@ _.extend(iD.Relation.prototype, {
type: 'relation',
members: [],
copy: function(deep, resolver, replacements) {
var copy = iD.Entity.prototype.copy.call(this);
if (!deep || !resolver || !this.isComplete(resolver)) {
return copy;
}
copy: function(resolver, copies) {
if (copies[this.id])
return copies[this.id];
var members = [],
i, oldmember, oldid, newid, children;
var copy = iD.Entity.prototype.copy.call(this, resolver, copies);
replacements = replacements || {};
replacements[this.id] = copy[0].id;
var members = this.members.map(function(member) {
return _.extend({}, member, {id: resolver.entity(member.id).copy(resolver, copies).id});
});
for (i = 0; i < this.members.length; i++) {
oldmember = this.members[i];
oldid = oldmember.id;
newid = replacements[oldid];
if (!newid) {
children = resolver.entity(oldid).copy(true, resolver, replacements);
newid = replacements[oldid] = children[0].id;
copy = copy.concat(children);
}
members.push({id: newid, type: oldmember.type, role: oldmember.role});
}
copy = copy.update({members: members});
copies[this.id] = copy;
copy[0] = copy[0].update({members: members});
return copy;
},

View File

@@ -12,29 +12,19 @@ _.extend(iD.Way.prototype, {
type: 'way',
nodes: [],
copy: function(deep, resolver) {
var copy = iD.Entity.prototype.copy.call(this);
copy: function(resolver, copies) {
if (copies[this.id])
return copies[this.id];
if (!deep || !resolver) {
return copy;
}
var copy = iD.Entity.prototype.copy.call(this, resolver, copies);
var nodes = [],
replacements = {},
i, oldid, newid, child;
var nodes = this.nodes.map(function(id) {
return resolver.entity(id).copy(resolver, copies).id;
});
for (i = 0; i < this.nodes.length; i++) {
oldid = this.nodes[i];
newid = replacements[oldid];
if (!newid) {
child = resolver.entity(oldid).copy();
newid = replacements[oldid] = child[0].id;
copy = copy.concat(child);
}
nodes.push(newid);
}
copy = copy.update({nodes: nodes});
copies[this.id] = copy;
copy[0] = copy[0].update({nodes: nodes});
return copy;
},

View File

@@ -140,7 +140,7 @@
<script src='../js/id/actions/change_tags.js'></script>
<script src='../js/id/actions/circularize.js'></script>
<script src='../js/id/actions/connect.js'></script>
<script src='../js/id/actions/copy_entity.js'></script>
<script src='../js/id/actions/copy_entities.js'></script>
<script src='../js/id/actions/delete_member.js'></script>
<script src='../js/id/actions/delete_multiple.js'></script>
<script src='../js/id/actions/delete_node.js'></script>
@@ -250,7 +250,7 @@
<script src='spec/actions/orthogonalize.js'></script>
<script src='spec/actions/straighten.js'></script>
<script src='spec/actions/connect.js'></script>
<script src="spec/actions/copy_entity.js"></script>
<script src="spec/actions/copy_entities.js"></script>
<script src='spec/actions/delete_member.js'></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>

View File

@@ -42,7 +42,7 @@
<script src="spec/actions/change_tags.js"></script>
<script src='spec/actions/circularize.js'></script>
<script src='spec/actions/connect.js'></script>
<script src="spec/actions/copy_entity.js"></script>
<script src="spec/actions/copy_entities.js"></script>
<script src='spec/actions/delete_member.js'></script>
<script src="spec/actions/delete_multiple.js"></script>
<script src="spec/actions/delete_node.js"></script>

View File

@@ -0,0 +1,71 @@
describe("iD.actions.CopyEntities", function () {
it("copies a node", function () {
var a = iD.Node({id: 'a'}),
base = iD.Graph([a]),
head = iD.actions.CopyEntities(['a'], base)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('a')).to.be.ok;
expect(created).to.have.length(1);
});
it("copies a way", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
base = iD.Graph([a, b, w]),
action = iD.actions.CopyEntities(['w'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('w')).to.be.ok;
expect(created).to.have.length(3);
});
it("copies multiple nodes", function () {
var base = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'})
]),
action = iD.actions.CopyEntities(['a', 'b'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('a')).to.be.ok;
expect(head.hasEntity('b')).to.be.ok;
expect(created).to.have.length(2);
});
it("copies multiple ways, keeping the same connections", function () {
var base = iD.Graph([
iD.Node({id: 'a'}),
iD.Node({id: 'b'}),
iD.Node({id: 'c'}),
iD.Way({id: 'w1', nodes: ['a', 'b']}),
iD.Way({id: 'w2', nodes: ['b', 'c']})
]),
action = iD.actions.CopyEntities(['w1', 'w2'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(created).to.have.length(5);
expect(action.copies().w1.nodes[1]).to.eql(action.copies().w2.nodes[0]);
});
it("obtains source entities from an alternate graph", function () {
var a = iD.Node({id: 'a'}),
old = iD.Graph([a]),
base = iD.Graph(),
action = iD.actions.CopyEntities(['a'], old),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('a')).not.to.be.ok;
expect(Object.keys(action.copies())).to.have.length(1);
});
});

View File

@@ -1,106 +0,0 @@
describe("iD.actions.CopyEntity", function () {
it("copies a Node and adds it to the graph", function () {
var a = iD.Node({id: 'a'}),
base = iD.Graph([a]),
head = iD.actions.CopyEntity('a', base, false)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('a')).to.be.ok;
expect(created).to.have.length(1);
expect(created[0]).to.be.an.instanceof(iD.Node);
});
it("shallow copies a Way and adds it to the graph", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
base = iD.Graph([a, b, w]),
head = iD.actions.CopyEntity('w', base, false)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('w')).to.be.ok;
expect(created).to.have.length(1);
expect(created[0]).to.be.an.instanceof(iD.Way);
});
it("deep copies a Way and child Nodes and adds them to the graph", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
base = iD.Graph([a, b, w]),
head = iD.actions.CopyEntity('w', base, true)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('w')).to.be.ok;
expect(created).to.have.length(3);
expect(created[0]).to.be.an.instanceof(iD.Way);
expect(created[1]).to.be.an.instanceof(iD.Node);
expect(created[2]).to.be.an.instanceof(iD.Node);
});
it("shallow copies a Relation and adds it to the graph", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
r = iD.Relation({id: 'r', members: [{id: 'w'}]}),
base = iD.Graph([a, b, w, r]),
head = iD.actions.CopyEntity('r', base, false)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(head.hasEntity('r')).to.be.ok;
expect(created).to.have.length(1);
expect(created[0]).to.be.an.instanceof(iD.Relation);
});
it("deep copies a Relation, member Ways, and child Nodes and adds them to the graph");//, function () {
// var a = iD.Node({id: 'a'}),
// b = iD.Node({id: 'b'}),
// w = iD.Way({id: 'w', nodes: ['a', 'b']}),
// r = iD.Relation({id: 'r', members: [{id: 'w'}]}),
// base = iD.Graph([a, b, w, r]),
// head = iD.actions.CopyEntity('r', base, true)(base),
// diff = iD.Difference(base, head),
// created = diff.created();
// expect(head.hasEntity('r')).to.be.ok;
// expect(created).to.have.length(4);
// expect(created[0]).to.be.an.instanceof(iD.Relation);
// expect(created[1]).to.be.an.instanceof(iD.Way);
// expect(created[2]).to.be.an.instanceof(iD.Node);
// expect(created[3]).to.be.an.instanceof(iD.Node);
// });
it("shallow copies from one graph to another", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
source = iD.Graph([a, b, w]),
base = iD.Graph(),
head = iD.actions.CopyEntity('w', source, false)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(created).to.have.length(1);
expect(created[0]).to.be.an.instanceof(iD.Way);
});
it("deep copies from one graph to another", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
source = iD.Graph([a, b, w]),
base = iD.Graph(),
head = iD.actions.CopyEntity('w', source, true)(base),
diff = iD.Difference(base, head),
created = diff.created();
expect(created).to.have.length(3);
expect(created[0]).to.be.an.instanceof(iD.Way);
expect(created[1]).to.be.an.instanceof(iD.Node);
expect(created[2]).to.be.an.instanceof(iD.Node);
});
});

View File

@@ -38,25 +38,43 @@ describe('iD.Entity', function () {
describe("#copy", function () {
it("returns a new Entity", function () {
var a = iD.Entity(),
result = a.copy();
expect(result).to.have.length(1);
expect(result[0]).to.be.an.instanceof(iD.Entity);
expect(a).not.to.equal(result[0]);
var n = iD.Entity({id: 'n'}),
result = n.copy(null, {});
expect(result).to.be.an.instanceof(iD.Entity);
expect(result).not.to.equal(n);
});
it("adds the new Entity to input object", function () {
var n = iD.Entity({id: 'n'}),
copies = {},
result = n.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(copies.n).to.equal(result);
});
it("returns an existing copy in input object", function () {
var n = iD.Entity({id: 'n'}),
copies = {},
result1 = n.copy(null, copies),
result2 = n.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(result1).to.equal(result2);
});
it("resets 'id', 'user', and 'version' properties", function () {
var a = iD.Entity({id: 'n1234', version: 10, user: 'bot-mode'}),
b = a.copy()[0];
expect(b.isNew()).to.be.ok;
expect(b.version).to.be.undefined;
expect(b.user).to.be.undefined;
var n = iD.Entity({id: 'n', version: 10, user: 'user'}),
copies = {};
n.copy(null, copies);
expect(copies.n.isNew()).to.be.ok;
expect(copies.n.version).to.be.undefined;
expect(copies.n.user).to.be.undefined;
});
it("copies tags", function () {
var a = iD.Entity({id: 'n1234', version: 10, user: 'test', tags: {foo: 'foo'}}),
b = a.copy()[0];
expect(b.tags).to.deep.equal(a.tags);
var n = iD.Entity({id: 'n', tags: {foo: 'foo'}}),
copies = {};
n.copy(null, copies);
expect(copies.n.tags).to.equal(n.tags);
});
});

View File

@@ -28,48 +28,47 @@ describe('iD.Relation', function () {
describe("#copy", function () {
it("returns a new Relation", function () {
var r1 = iD.Relation({id: 'r1'}),
result = r1.copy(),
r2 = result[0];
var r = iD.Relation({id: 'r'}),
result = r.copy(null, {});
expect(result).to.have.length(1);
expect(r2).to.be.an.instanceof(iD.Relation);
expect(r1).not.to.equal(r2);
expect(result).to.be.an.instanceof(iD.Relation);
expect(result).not.to.equal(r);
});
it("keeps same members when deep = false", function () {
it("adds the new Relation to input object", function () {
var r = iD.Relation({id: 'r'}),
copies = {},
result = r.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(copies.r).to.equal(result);
});
it("returns an existing copy in input object", function () {
var r = iD.Relation({id: 'r'}),
copies = {},
result1 = r.copy(null, copies),
result2 = r.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(result1).to.equal(result2);
});
it("deep copies members", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
r1 = iD.Relation({id: 'r1', members: [{id: 'w1', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w1, r1]),
result = r1.copy(),
r1_copy = result[0];
w = iD.Way({id: 'w', nodes: ['a','b','c','a']}),
r = iD.Relation({id: 'r', members: [{id: 'w', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w, r]),
copies = {}
result = r.copy(graph, copies);
expect(result).to.have.length(1);
expect(r1.members).to.deep.equal(r1_copy.members);
});
it("makes new members when deep = true", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
r1 = iD.Relation({id: 'r1', members: [{id: 'w1', role: 'outer'}]}),
graph = iD.Graph([a, b, c, w1, r1]),
result = r1.copy(true, graph),
r1_copy = result[0];
expect(result).to.have.length(5);
expect(result[0]).to.be.an.instanceof(iD.Relation);
expect(result[1]).to.be.an.instanceof(iD.Way);
expect(result[2]).to.be.an.instanceof(iD.Node);
expect(result[3]).to.be.an.instanceof(iD.Node);
expect(result[4]).to.be.an.instanceof(iD.Node);
expect(r1_copy.members[0].id).not.to.equal(r1.members[0].id);
expect(r1_copy.members[0].role).to.equal(r1.members[0].role);
expect(Object.keys(copies)).to.have.length(5);
expect(copies.w).to.be.an.instanceof(iD.Way);
expect(copies.a).to.be.an.instanceof(iD.Node);
expect(copies.b).to.be.an.instanceof(iD.Node);
expect(copies.c).to.be.an.instanceof(iD.Node);
expect(result.members[0].id).not.to.equal(r.members[0].id);
expect(result.members[0].role).to.equal(r.members[0].role);
});
it("deep copies non-tree relation graphs without duplicating children", function () {
@@ -77,48 +76,39 @@ describe('iD.Relation', function () {
r1 = iD.Relation({id: 'r1', members: [{id: 'r2'}, {id: 'w'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'w'}]}),
graph = iD.Graph([w, r1, r2]),
result = r1.copy(true, graph),
r1_copy = result[0],
r2_copy = result[1],
w_copy = result[2];
copies = {};
r1.copy(graph, copies);
expect(result).to.have.length(3);
expect(r1_copy).to.be.an.instanceof(iD.Relation);
expect(r2_copy).to.be.an.instanceof(iD.Relation);
expect(w_copy).to.be.an.instanceof(iD.Way);
expect(r1_copy.members[0].id).to.equal(r2_copy.id);
expect(r1_copy.members[1].id).to.equal(r2_copy.members[0].id);
expect(Object.keys(copies)).to.have.length(3);
expect(copies.r1).to.be.an.instanceof(iD.Relation);
expect(copies.r2).to.be.an.instanceof(iD.Relation);
expect(copies.w).to.be.an.instanceof(iD.Way);
expect(copies.r1.members[0].id).to.equal(copies.r2.id);
expect(copies.r1.members[1].id).to.equal(copies.w.id);
expect(copies.r2.members[0].id).to.equal(copies.w.id);
});
it("deep copies cyclical relation graphs without issue"); //, function () {
// var r1 = iD.Relation({id: 'r1', members: [{id: 'r2'}]}),
// r2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
// graph = iD.Graph([r1, r2]),
// result = r1.copy(true, graph),
// r1_copy = result[0],
// r2_copy = result[1];
it("deep copies cyclical relation graphs without issue", function () {
var r1 = iD.Relation({id: 'r1', members: [{id: 'r2'}]}),
r2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}),
graph = iD.Graph([r1, r2]),
copies = {};
r1.copy(graph, copies);
// expect(result).to.have.length(2);
// expect(r1_copy).to.be.an.instanceof(iD.Relation);
// expect(r2_copy).to.be.an.instanceof(iD.Relation);
expect(Object.keys(copies)).to.have.length(2);
expect(copies.r1.members[0].id).to.equal(copies.r2.id);
expect(copies.r2.members[0].id).to.equal(copies.r1.id);
});
// var msg = 'r1_copy = ' + JSON.stringify(r1_copy) +
// 'r2_copy = ' + JSON.stringify(r2_copy);
// expect(r1_copy.members[0].id).to.equal(r2_copy.id, msg);
// expect(r2_copy.members[0].id).to.equal(r1_copy.id, msg);
// });
it("deep copies self-referencing relations without issue", function () {
var r = iD.Relation({id: 'r', members: [{id: 'r'}]}),
graph = iD.Graph([r]),
copies = {};
r.copy(graph, copies);
it("deep copies self-refrencing relations without issue"); //, function () {
// var r1 = iD.Relation({id: 'r1', members: [{id: 'r1'}]}),
// graph = iD.Graph([r1]),
// result = r1.copy(true, graph),
// r1_copy = result[0];
// expect(result).to.have.length(1);
// expect(r1_copy).to.be.an.instanceof(iD.Relation);
// expect(r1_copy.members[0].id).to.equal(r1_copy.id);
// });
expect(Object.keys(copies)).to.have.length(1);
expect(copies.r.members[0].id).to.equal(copies.r.id);
});
});
describe("#extent", function () {

View File

@@ -28,47 +28,54 @@ describe('iD.Way', function() {
describe("#copy", function () {
it("returns a new Way", function () {
var w1 = iD.Way({id: 'w1'}),
result = w1.copy(),
w2 = result[0];
var w = iD.Way({id: 'w'}),
result = w.copy(null, {});
expect(result).to.have.length(1);
expect(w2).to.be.an.instanceof(iD.Way);
expect(w1).not.to.equal(w2);
expect(result).to.be.an.instanceof(iD.Way);
expect(result).not.to.equal(w);
});
it("keeps same nodes when deep = false", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
graph = iD.Graph([a, b, c, w1]),
result = w1.copy(),
w2 = result[0];
expect(result).to.have.length(1);
expect(w1.nodes).to.deep.equal(w2.nodes);
it("adds the new Way to input object", function () {
var w = iD.Way({id: 'w'}),
copies = {},
result = w.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(copies.w).to.equal(result);
});
it("makes new nodes when deep = true", function () {
it("returns an existing copy in input object", function () {
var w = iD.Way({id: 'w'}),
copies = {},
result1 = w.copy(null, copies),
result2 = w.copy(null, copies);
expect(Object.keys(copies)).to.have.length(1);
expect(result1).to.equal(result2);
});
it("deep copies nodes", function () {
var a = iD.Node({id: 'a'}),
b = iD.Node({id: 'b'}),
c = iD.Node({id: 'c'}),
w1 = iD.Way({id: 'w1', nodes: ['a','b','c','a']}),
graph = iD.Graph([a, b, c, w1]),
result = w1.copy(true, graph),
w2 = result[0];
w = iD.Way({id: 'w', nodes: ['a', 'b']}),
graph = iD.Graph([a, b, w]),
copies = {},
result = w.copy(graph, copies);
expect(result).to.have.length(4);
expect(result[0]).to.be.an.instanceof(iD.Way);
expect(result[1]).to.be.an.instanceof(iD.Node);
expect(result[2]).to.be.an.instanceof(iD.Node);
expect(result[3]).to.be.an.instanceof(iD.Node);
expect(Object.keys(copies)).to.have.length(3);
expect(copies.a).to.be.an.instanceof(iD.Node);
expect(copies.b).to.be.an.instanceof(iD.Node);
expect(copies.a).not.to.equal(w.nodes[0]);
expect(copies.b).not.to.equal(w.nodes[1]);
expect(result.nodes).to.deep.eql([copies.a.id, copies.b.id]);
});
expect(w2.nodes[0]).not.to.equal(w1.nodes[0]);
expect(w2.nodes[1]).not.to.equal(w1.nodes[1]);
expect(w2.nodes[2]).not.to.equal(w1.nodes[2]);
expect(w2.nodes[3]).to.equal(w2.nodes[0]);
it("creates only one copy of shared nodes", function () {
var a = iD.Node({id: 'a'}),
w = iD.Way({id: 'w', nodes: ['a', 'a']}),
graph = iD.Graph([a, w]),
copies = {},
result = w.copy(graph, copies);
expect(result.nodes[0]).to.equal(result.nodes[1]);
});
});