text: 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({ tags: { yes: 'no' } }),
                  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({ tags: { yes: 'no' } }),
                  base = iD.Graph([n1]),
                  head = base.replace(n2),
                  diff = iD.Difference(head, base);
              expect(diff.changes()).to.eql({n: {base: n2, head: n1}});
          });
  
          it("doesn't include updated but identical 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({});
          });
  
          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({});
          });
  
          it("doesn't include created entities that were subsequently reverted", function () {
              var node = iD.Node({id: 'n-1'}),
                  base = iD.Graph(),
                  head = base.replace(node).revert('n-1'),
                  diff = iD.Difference(base, head);
              expect(diff.changes()).to.eql({});
          });
  
          it("doesn't include modified entities that were subsequently reverted", function () {
              var n1 = iD.Node({id: 'n'}),
                  n2 = n1.update({ tags: { yes: 'no' } }),
                  base = iD.Graph([n1]),
                  head = base.replace(n2).revert('n'),
                  diff = iD.Difference(base, head);
              expect(diff.changes()).to.eql({});
          });
  
          it("doesn't include deleted entities that were subsequently reverted", function () {
              var node = iD.Node({id: 'n'}),
                  base = iD.Graph([node]),
                  head = base.remove(node).revert('n'),
                  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("#summary", function () {
          var base = iD.Graph([
              iD.Node({id: 'a', tags: {crossing: 'zebra'}}),
              iD.Node({id: 'b'}),
              iD.Node({id: 'v'}),
              iD.Way({id: '-', nodes: ['a', 'b']})
          ]);
  
          it("reports a created way as created", function() {
              var way = iD.Way({id: '+'}),
                  head = base.replace(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'created',
                  entity: way,
                  graph: head
              }]);
          });
  
          it("reports a deleted way as deleted", function() {
              var way = base.entity('-'),
                  head = base.remove(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'deleted',
                  entity: way,
                  graph: base
              }]);
          });
  
          it("reports a modified way as modified", function() {
              var way = base.entity('-').mergeTags({highway: 'primary'}),
                  head = base.replace(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: way,
                  graph: head
              }]);
          });
  
          it("reports a way as modified when a member vertex is moved", function() {
              var vertex = base.entity('b').move([0,3]),
                  head = base.replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: head.entity('-'),
                  graph: head
              }]);
          });
  
          it("reports a way as modified when a member vertex is added", function() {
              var vertex = iD.Node({id: 'c'}),
                  way = base.entity('-').addNode('c'),
                  head = base.replace(vertex).replace(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: way,
                  graph: head
              }]);
          });
  
          it("reports a way as modified when a member vertex is removed", function() {
              var way = base.entity('-').removeNode('b'),
                  head = base.replace(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: way,
                  graph: head
              }]);
          });
  
          it("reports a created way containing a moved vertex as being created", function() {
              var vertex = base.entity('b').move([0,3]),
                  way = iD.Way({id: '+', nodes: ['b']}),
                  head = base.replace(way).replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'created',
                  entity: way,
                  graph: head
              }, {
                  changeType: 'modified',
                  entity: head.entity('-'),
                  graph: head
              }]);
          });
  
          it("reports a created way with a created vertex as being created", function() {
              var vertex = iD.Node({id: 'c'}),
                  way = iD.Way({id: '+', nodes: ['c']}),
                  head = base.replace(vertex).replace(way),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'created',
                  entity: way,
                  graph: head
              }]);
          });
  
          it("reports a vertex as modified when it has tags and they are changed", function() {
              var vertex = base.entity('a').mergeTags({highway: 'traffic_signals'}),
                  head = base.replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: vertex,
                  graph: head
              }]);
          });
  
          it("reports a vertex as modified when it has tags and is moved", function() {
              var vertex = base.entity('a').move([1, 2]),
                  head = base.replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: head.entity('-'),
                  graph: head
              }, {
                  changeType: 'modified',
                  entity: vertex,
                  graph: head
              }]);
          });
  
          it("does not report a vertex as modified when it is moved and has no-op tag changes", function() {
              var vertex = base.entity('b').update({tags: {}, loc: [1, 2]}),
                  head = base.replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: head.entity('-'),
                  graph: head
              }]);
          });
  
          it("reports a vertex as deleted when it had tags", function() {
              var vertex = base.entity('v'),
                  head = base.remove(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'deleted',
                  entity: vertex,
                  graph: base
              }]);
          });
  
          it("reports a vertex as created when it has tags", function() {
              var vertex = iD.Node({id: 'c', tags: {crossing: 'zebra'}}),
                  way = base.entity('-').addNode('c'),
                  head = base.replace(way).replace(vertex),
                  diff = iD.Difference(base, head);
  
              expect(diff.summary()).to.eql([{
                  changeType: 'modified',
                  entity: way,
                  graph: head
              }, {
                  changeType: 'created',
                  entity: vertex,
                  graph: head
              }]);
          });
      });
  
      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");
      });
  });
  