diff --git a/data/core.yaml b/data/core.yaml index 5b544040b..820e0fc0d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -229,6 +229,11 @@ en: annotation: create: Added a turn restriction delete: Deleted a turn restriction + detachNode: + title: Detach + key: D + description: Detach this node from these lines/areas. + annotation: Detached a node from owning lines/areas. restriction: controls: distance: Distance diff --git a/dist/locales/en.json b/dist/locales/en.json index 3412491a4..8abaa9db5 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -297,6 +297,12 @@ "create": "Added a turn restriction", "delete": "Deleted a turn restriction" } + }, + "detachNode": { + "title": "Detach", + "key": "D", + "description": "Detach this node from these lines/areas.", + "annotation": "Detached a node from owning lines/areas." } }, "restriction": { diff --git a/modules/actions/detach_node.js b/modules/actions/detach_node.js new file mode 100644 index 000000000..d83fd46ed --- /dev/null +++ b/modules/actions/detach_node.js @@ -0,0 +1,27 @@ +import { osmNode } from '../osm'; + +export function actionDetachNode(nodeId) { + return function (graph) { + // Get the point in question + var node = graph.entity(nodeId); + // Get all of the ways it's currently attached to + var parentWays = graph.parentWays(node); + // Create a new node to replace the one we will detach + var replacementNode = osmNode({ loc: node.loc }); + // We need to process each way in turn, updating the graph as we go + return parentWays + .reduce(function (accGraph, parentWay) { + // Make a note of where in the way our target node is inside this way + var originalIndex = parentWay.nodes.indexOf(nodeId); + // Swap out the target node for the replacement + var updatedWay = parentWay + .removeNode(nodeId) // Remove our target node from the parent way + .addNode(replacementNode.id, originalIndex); // Add in the replacement node in its place + // Update the graph with the updated way + return accGraph.replace(updatedWay); + }, + // Seed the reduction with the input graph, updated to include the replacementNode so + // that is accessible to the ways when we add it in to them + graph.replace(replacementNode)); + }; +} diff --git a/modules/actions/index.js b/modules/actions/index.js index 330690db2..4f15456d8 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -33,3 +33,4 @@ export { actionSplit } from './split'; export { actionStraighten } from './straighten'; export { actionUnrestrictTurn } from './unrestrict_turn'; export { actionReflect } from './reflect.js'; +export { actionDetachNode } from './detach_node'; \ No newline at end of file diff --git a/modules/operations/detach_node.js b/modules/operations/detach_node.js new file mode 100644 index 000000000..28ca9b600 --- /dev/null +++ b/modules/operations/detach_node.js @@ -0,0 +1,50 @@ +import { actionDetachNode } from '../actions/index'; +import { behaviorOperation } from '../behavior/index'; +import { modeMove } from '../modes/index'; +import { t } from '../util/locale'; + +export function operationDetachNode(selectedIDs, context) { + var selectedNode = selectedIDs[0]; + var operation = function () { + context.perform(actionDetachNode(selectedNode)); + context.enter(modeMove(context, [selectedNode], context.graph)); + }; + var hasTags = function (entity) { + return Object.keys(entity.tags).length > 0; + }; + operation.available = function () { + // Check multiple items aren't selected + if (selectedIDs.length !== 1) { + return false; + } + // Get the entity itself + var graph = context.graph(); + var entity = graph.hasEntity(selectedNode); + if (!entity) { + // This probably isn't possible + return false; + } + // Confirm entity is a node with tags + if (entity.type === 'node' && hasTags(entity)) { + // Confirm that the node is owned by at least 1 parent way + var parentWays = graph.parentWays(entity); + return parentWays && parentWays.length > 0; + } + // Not appropriate + return false; + }; + operation.disabled = function () { + return false; + }; + operation.tooltip = function () { + return t('operations.detachNode.description'); + }; + operation.annotation = function () { + return t('operations.detachNode.annotation'); + }; + operation.id = 'detachNode'; + operation.keys = [t('operations.detachNode.key')]; + operation.title = t('operations.detachNode.title'); + operation.behavior = behaviorOperation(context).which(operation); + return operation; +} diff --git a/modules/operations/index.js b/modules/operations/index.js index 1ad24cd97..8339d8205 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -10,3 +10,4 @@ export { operationReverse } from './reverse'; export { operationRotate } from './rotate'; export { operationSplit } from './split'; export { operationStraighten } from './straighten'; +export { operationDetachNode } from './detach_node'; diff --git a/svg/iD-sprite/operations/operation-detachNode.svg b/svg/iD-sprite/operations/operation-detachNode.svg new file mode 100644 index 000000000..fea5ba873 --- /dev/null +++ b/svg/iD-sprite/operations/operation-detachNode.svg @@ -0,0 +1,61 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/test/index.html b/test/index.html index 188b9084c..73fd6cc59 100644 --- a/test/index.html +++ b/test/index.html @@ -1,5 +1,6 @@ + Mocha Tests @@ -7,6 +8,7 @@ +
@@ -17,9 +19,9 @@ @@ -60,6 +62,7 @@ + @@ -138,7 +141,8 @@ - + + \ No newline at end of file diff --git a/test/spec/actions/detach_node.js b/test/spec/actions/detach_node.js new file mode 100644 index 000000000..70739b640 --- /dev/null +++ b/test/spec/actions/detach_node.js @@ -0,0 +1,540 @@ +describe('iD.actionDetachNode', function () { + var tags = { 'name': 'test' }; + function createTargetNode(id, lonlat) { + return iD.Node({ id: id, loc: lonlat, tags: tags }); + } + describe('simple way', function () { + var graph; + beforeEach(function () { + // Set up a simple way + // a-b-c-d + // (0,0)-(1,0)-(2,0)-(3,0) + graph = iD.Graph([ + iD.Node({ id: 'a', loc: [0, 0] }), + iD.Node({ id: 'b', loc: [1, 0] }), + iD.Node({ id: 'c', loc: [2, 0] }), + iD.Node({ id: 'd', loc: [3, 0] }), + iD.Way({ id: 'w', nodes: ['a', 'b', 'c', 'd'] }) + ]); + }); + + describe('target in first position', function () { + beforeEach(function () { + // Swap target into the location & position of A + var targetNode = createTargetNode('a', graph.entity('a').loc); + graph = graph.replace(targetNode); + }); + + it('does not change length of way', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the way still has 4 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(4); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[1]).to.eql('b'); + expect(target.nodes[2]).to.eql('c'); + expect(target.nodes[3]).to.eql('d'); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([2, 0]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([3, 0]); + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "a" + expect(nodes[0]).not.to.eql('a'); + // and that the tags are not present + expect(assertionGraph.entity(nodes[0]).tags).to.eql({}); + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('a'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([0, 0]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); + + describe('target in second position', function () { + beforeEach(function () { + // Swap target into the location & position of B + var targetNode = createTargetNode('b', graph.entity('b').loc); + graph = graph.replace(targetNode); + }); + + it('does not change length of way', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the way still has 4 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(4); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[0]).to.eql('a'); + expect(target.nodes[2]).to.eql('c'); + expect(target.nodes[3]).to.eql('d'); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([2, 0]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([3, 0]); + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "a" + expect(nodes[1]).not.to.eql('b'); + // and that the tags are not present + expect(assertionGraph.entity(nodes[1]).tags).to.eql({}); + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('b'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([1, 0]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); + }); + describe('closed way', function () { + var graph; + beforeEach(function () { + // Set up a closed way + // a-b (0,0)-(1,0) + // | | + // d-c (0,1)-(1,1) + graph = iD.Graph([ + iD.Node({ id: 'a', loc: [0, 0] }), + iD.Node({ id: 'b', loc: [1, 0] }), + iD.Node({ id: 'c', loc: [1, 1] }), + iD.Node({ id: 'd', loc: [0, 1] }), + iD.Way({ id: 'w', nodes: ['a', 'b', 'c', 'd', 'a'] }) + ]); + }); + + describe('target in first position', function () { + beforeEach(function () { + // Swap target into the location & position of A + var targetNode = createTargetNode('a', graph.entity('a').loc); + graph = graph.replace(targetNode); + }); + + it('does not change length of way', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the way still has 5 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(5); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[1]).to.eql('b'); + expect(target.nodes[2]).to.eql('c'); + expect(target.nodes[3]).to.eql('d'); + // Need to confirm that the id of the first & last node is the same so that the way remains closed + expect(target.nodes[0]).to.eql(target.nodes[4]); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([1, 1]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([0, 1]); + // We don't need to assert node[4] location as we've already confirmed that it is the same as node 0 + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "a" + expect(nodes[0]).not.to.eql('a'); + // .. also in the tail position + expect(nodes[4]).not.to.eql('a'); + // and that the tags are not present (already confirmed same node in position 0 & 4, so only need to check tags once) + expect(assertionGraph.entity(nodes[0]).tags).to.eql({}); + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('a')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('a'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([0, 0]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); + + describe('target in second position', function () { + beforeEach(function () { + // Swap target into the location & position of B + var targetNode = createTargetNode('b', graph.entity('b').loc); + graph = graph.replace(targetNode); + }); + + it('does not change length of way', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the way still has 5 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(5); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[0]).to.eql('a'); + expect(target.nodes[2]).to.eql('c'); + expect(target.nodes[3]).to.eql('d'); + expect(target.nodes[4]).to.eql('a'); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([1, 1]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([0, 1]); + // Confirmed already that node[4] is node[0] so no further assertion needed + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "a" + expect(nodes[1]).not.to.eql('b'); + // and that the tags are not present + expect(assertionGraph.entity(nodes[1]).tags).to.eql({}); + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('b')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('b'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([1, 0]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); + }); + describe('intersecting simple ways', function () { + var graph; + beforeEach(function () { + // Set up two simple ways + // a-b-c-d (0,0)-(1,0)-(2,0)-(3,0) + // e (2,1) + // f (2,2) + // Node c represents the target + graph = iD.Graph([ + iD.Node({ id: 'a', loc: [0, 0] }), + iD.Node({ id: 'b', loc: [1, 0] }), + iD.Node({ id: 'c', loc: [2, 0], tags: tags }), + iD.Node({ id: 'd', loc: [3, 0] }), + iD.Node({ id: 'e', loc: [2, 1] }), + iD.Node({ id: 'f', loc: [2, 2] }), + iD.Way({ id: 'w', nodes: ['a', 'b', 'c', 'd'] }), + iD.Way({ id: 'x', nodes: ['c', 'e', 'f'] }) + ]); + }); + + it('does not change length of ways', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the way still has 4 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(4); + // .. and second way has 3 + target = assertionGraph.entity('x'); + expect(target.nodes.length).to.eql(3); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[0]).to.eql('a'); + expect(target.nodes[1]).to.eql('b'); + expect(target.nodes[3]).to.eql('d'); + // and second way + target = assertionGraph.entity('x'); + expect(target.nodes[1]).to.eql('e'); + expect(target.nodes[2]).to.eql('f'); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([2, 0]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([3, 0]); + // and second way + nodes = assertionGraph.entity('x').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([2, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([2, 1]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([2, 2]); + }); + + it('uses same replacement node at intersection', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + // Confirm both ways have the same replacement node + expect(assertionGraph.entity('w').nodes[2]).to.eql(assertionGraph.entity('x').nodes[0]); + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "c" + expect(nodes[2]).not.to.eql('c'); + // and that the tags are not present + expect(assertionGraph.entity(nodes[2]).tags).to.eql({}); + // Confirm that the second way's first node is the same + expect(assertionGraph.entity('x').nodes[0]).to.eql(nodes[2]); + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('c'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([2, 0]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); + describe('intersecting closed way', function () { + var graph; + beforeEach(function () { + // Set up two intersecting closed ways + // a-b (0,0)-(1,0) + // | | + // d-c-e (0,1)-(1,1)-(2,1) + // | | + // g f (0,2) - (1,2) + // C is the target node + graph = iD.Graph([ + iD.Node({ id: 'a', loc: [0, 0] }), + iD.Node({ id: 'b', loc: [1, 0] }), + iD.Node({ id: 'c', loc: [1, 1], tags: tags }), + iD.Node({ id: 'd', loc: [0, 1] }), + iD.Node({ id: 'e', loc: [2, 1] }), + iD.Node({ id: 'f', loc: [1, 2] }), + iD.Node({ id: 'g', loc: [0, 2] }), + iD.Way({ id: 'w', nodes: ['a', 'b', 'c', 'd', 'a'] }), + iD.Way({ id: 'x', nodes: ['c', 'e', 'f', 'g', 'c'] }) + ]); + }); + + it('does not change length of ways', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the way still has 5 nodes + var target = assertionGraph.entity('w'); + expect(target.nodes.length).to.eql(5); + // and the second + target = assertionGraph.entity('x'); + expect(target.nodes.length).to.eql(5); + }); + + it('does not change order of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the way is ordered correctly + var target = assertionGraph.entity('w'); + // Note that we can't be sure of the id of the replacement node + // so we only assert the nodes we know the ids for + // As we have already confirmed the size of the array we can assume + // that the replacement node is in the correct posisiton by a process of elimination + expect(target.nodes[0]).to.eql('a'); + expect(target.nodes[1]).to.eql('b'); + expect(target.nodes[3]).to.eql('d'); + // Need to confirm that the id of the first & last node is the same so that the way remains closed + expect(target.nodes[0]).to.eql(target.nodes[4]); + // and the same for the other way + target = assertionGraph.entity('x'); + expect(target.nodes[1]).to.eql('e'); + expect(target.nodes[2]).to.eql('f'); + expect(target.nodes[3]).to.eql('g'); + expect(target.nodes[0]).to.eql(target.nodes[4]); + }); + + it('does not change location of nodes', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // Confirm that the nodes have not moved, including the replacement node + var nodes = assertionGraph.entity('w').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([0, 0]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([1, 0]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([1, 1]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([0, 1]); + // We don't need to assert node[4] location as we've already confirmed that it is the same as node 0 + // and the other way + nodes = assertionGraph.entity('x').nodes; + expect(assertionGraph.entity(nodes[0]).loc).to.eql([1, 1]); + expect(assertionGraph.entity(nodes[1]).loc).to.eql([2, 1]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([1, 2]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([0, 2]); + }); + + it('uses same replacement node at intersection', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + // Confirm both ways have the same replacement node + expect(assertionGraph.entity('w').nodes[2]).to.eql(assertionGraph.entity('x').nodes[0]); + }); + + it('does replace target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + var nodes = assertionGraph.entity('w').nodes; + // Confirm that the target is no longer "c" + expect(nodes[0]).not.to.eql('c'); + // .. also in the tail position + expect(nodes[4]).not.to.eql('c'); + // and that the tags are not present (already confirmed same node in position 0 & 4, so only need to check tags once) + expect(assertionGraph.entity(nodes[0]).tags).to.eql({}); + // Don't need to check for way 2 since we've already confirmed it is the same node + }); + + it('does detach target node', function () { + // Act + var assertionGraph = iD.actionDetachNode('c')(graph); + + // confirm that a still exists + var targetNode = assertionGraph.entity('c'); + expect(targetNode).not.to.eql(undefined); + // .., and that the location is correct + expect(targetNode.loc).to.eql([1, 1]); + // ... and that the tags are intact + expect(targetNode.tags).to.eql(tags); + // ... and that the parentWay is empty + expect(assertionGraph.parentWays(targetNode)).to.eql([]); + }); + }); +}); \ No newline at end of file