diff --git a/data/core.yaml b/data/core.yaml index f06a69983..828e9715b 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -229,12 +229,13 @@ en: annotation: create: Added a turn restriction delete: Deleted a turn restriction - detachNode: + detach_node: title: Detach - key: T + key: E description: Detach this node from these lines/areas. - annotation: Detached a node from owning lines/areas. - via_restriction: "This can't be detached because it would damage a turn restriction." + annotation: Detached a node from parent lines/areas. + restriction: "This node can't be detached because it would damage a \"{relation}\" relation." + connected_to_hidden: This node can't be detached because it is connected to a hidden feature. restriction: controls: distance: Distance @@ -1140,6 +1141,7 @@ en: continue_line: "Continue a line at the selected node" merge: "Combine (merge) selected features" disconnect: "Disconnect features at the selected node" + detach_node: "Detach selected node from parent lines/areas" split: "Split a line into two at the selected node" reverse: "Reverse a line" move: "Move selected features" diff --git a/data/shortcuts.json b/data/shortcuts.json index ee0c9cdc9..0da3dfdce 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -123,23 +123,19 @@ "text": "shortcuts.browsing.vertex_selected.previous" }, { - "shortcuts": ["]","↘" - ], + "shortcuts": ["]","↘"], "text": "shortcuts.browsing.vertex_selected.next" }, { - "shortcuts": ["{","⇞" - ], + "shortcuts": ["{","⇞"], "text": "shortcuts.browsing.vertex_selected.first" }, { - "shortcuts": ["}","⇟" - ], + "shortcuts": ["}","⇟"], "text": "shortcuts.browsing.vertex_selected.last" }, { - "shortcuts": ["\\","shortcuts.key.pause" - ], + "shortcuts": ["\\","shortcuts.key.pause"], "text": "shortcuts.browsing.vertex_selected.change_parent" } ] @@ -157,33 +153,27 @@ "text": "shortcuts.editing.drawing.title" }, { - "shortcuts": ["1" - ], + "shortcuts": ["1"], "text": "shortcuts.editing.drawing.add_point" }, { - "shortcuts": ["2" - ], + "shortcuts": ["2"], "text": "shortcuts.editing.drawing.add_line" }, { - "shortcuts": ["3" - ], + "shortcuts": ["3"], "text": "shortcuts.editing.drawing.add_area" }, { - "shortcuts": ["Left-click","shortcuts.key.space" - ], + "shortcuts": ["Left-click","shortcuts.key.space"], "text": "shortcuts.editing.drawing.place_point" }, { - "shortcuts": ["⌥" - ], + "shortcuts": ["⌥"], "text": "shortcuts.editing.drawing.disable_snap" }, { - "shortcuts": ["↵","⎋" - ], + "shortcuts": ["↵","⎋"], "text": "shortcuts.editing.drawing.stop_line" }, { @@ -192,32 +182,27 @@ }, { "modifiers": ["⌘"], - "shortcuts": ["C" - ], + "shortcuts": ["C"], "text": "shortcuts.editing.commands.copy" }, { "modifiers": ["⌘"], - "shortcuts": ["V" - ], + "shortcuts": ["V"], "text": "shortcuts.editing.commands.paste" }, { "modifiers": ["⌘"], - "shortcuts": ["Z" - ], + "shortcuts": ["Z"], "text": "shortcuts.editing.commands.undo" }, { "modifiers": ["⌘","⇧"], - "shortcuts": ["Z" - ], + "shortcuts": ["Z"], "text": "shortcuts.editing.commands.redo" }, { "modifiers": ["⌘"], - "shortcuts": ["S" - ], + "shortcuts": ["S"], "text": "shortcuts.editing.commands.save" } ] @@ -229,64 +214,56 @@ "text": "shortcuts.editing.operations.title" }, { - "shortcuts": ["operations.continue.key" - ], + "shortcuts": ["operations.continue.key"], "text": "shortcuts.editing.operations.continue_line" }, { - "shortcuts": ["operations.merge.key" - ], + "shortcuts": ["operations.merge.key"], "text": "shortcuts.editing.operations.merge" }, { - "shortcuts": ["operations.disconnect.key" - ], + "shortcuts": ["operations.disconnect.key"], "text": "shortcuts.editing.operations.disconnect" }, { - "shortcuts": ["operations.split.key" - ], + "shortcuts": ["operations.detach_node.key"], + "text": "shortcuts.editing.operations.detach_node" + }, + { + "shortcuts": ["operations.split.key"], "text": "shortcuts.editing.operations.split" }, { - "shortcuts": ["operations.reverse.key" - ], + "shortcuts": ["operations.reverse.key"], "text": "shortcuts.editing.operations.reverse" }, { - "shortcuts": ["operations.move.key" - ], + "shortcuts": ["operations.move.key"], "text": "shortcuts.editing.operations.move" }, { - "shortcuts": ["operations.rotate.key" - ], + "shortcuts": ["operations.rotate.key"], "text": "shortcuts.editing.operations.rotate" }, { - "shortcuts": ["operations.orthogonalize.key" - ], + "shortcuts": ["operations.orthogonalize.key"], "text": "shortcuts.editing.operations.orthogonalize" }, { - "shortcuts": ["operations.circularize.key" - ], + "shortcuts": ["operations.circularize.key"], "text": "shortcuts.editing.operations.circularize" }, { - "shortcuts": ["operations.reflect.key.long" - ], + "shortcuts": ["operations.reflect.key.long"], "text": "shortcuts.editing.operations.reflect_long" }, { - "shortcuts": ["operations.reflect.key.short" - ], + "shortcuts": ["operations.reflect.key.short"], "text": "shortcuts.editing.operations.reflect_short" }, { "modifiers": ["⌘"], - "shortcuts": ["⌫" - ], + "shortcuts": ["⌫"], "text": "shortcuts.editing.operations.delete" } ] @@ -305,32 +282,27 @@ }, { "modifiers": ["⌘"], - "shortcuts": ["info_panels.key" - ], + "shortcuts": ["info_panels.key"], "text": "shortcuts.tools.info.all" }, { "modifiers": ["⌘","⇧"], - "shortcuts": ["info_panels.background.key" - ], + "shortcuts": ["info_panels.background.key"], "text": "shortcuts.tools.info.background" }, { "modifiers": ["⌘","⇧"], - "shortcuts": ["info_panels.history.key" - ], + "shortcuts": ["info_panels.history.key"], "text": "shortcuts.tools.info.history" }, { "modifiers": ["⌘","⇧"], - "shortcuts": ["info_panels.location.key" - ], + "shortcuts": ["info_panels.location.key"], "text": "shortcuts.tools.info.location" }, { "modifiers": ["⌘","⇧"], - "shortcuts": ["info_panels.measurement.key" - ], + "shortcuts": ["info_panels.measurement.key"], "text": "shortcuts.tools.info.measurement" } ] diff --git a/dist/locales/en.json b/dist/locales/en.json index 8363a101f..47a1f0100 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -298,12 +298,13 @@ "delete": "Deleted a turn restriction" } }, - "detachNode": { + "detach_node": { "title": "Detach", - "key": "T", + "key": "E", "description": "Detach this node from these lines/areas.", - "annotation": "Detached a node from owning lines/areas.", - "via_restriction": "This can't be detached because it would damage a turn restriction." + "annotation": "Detached a node from parent lines/areas.", + "restriction": "This node can't be detached because it would damage a \"{relation}\" relation.", + "connected_to_hidden": "This node can't be detached because it is connected to a hidden feature." } }, "restriction": { @@ -1315,6 +1316,7 @@ "continue_line": "Continue a line at the selected node", "merge": "Combine (merge) selected features", "disconnect": "Disconnect features at the selected node", + "detach_node": "Detach selected node from parent lines/areas", "split": "Split a line into two at the selected node", "reverse": "Reverse a line", "move": "Move selected features", diff --git a/modules/actions/detach_node.js b/modules/actions/detach_node.js index 7ff5cd8e3..39bcd59fa 100644 --- a/modules/actions/detach_node.js +++ b/modules/actions/detach_node.js @@ -1,39 +1,48 @@ 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); + +export function actionDetachNode(nodeID) { + + var action = function(graph) { + var node = graph.entity(nodeID); + // 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 - var updatedWaysGraph = 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 and pass into the next cycle of the reduce operation - 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)); + var replacement = osmNode({ loc: node.loc }); + graph = graph.replace(replacement); + + // Process each way in turn, updating the graph as we go + graph = graph.parentWays(node) + .reduce(function(accGraph, parentWay) { + return accGraph.replace(parentWay.replaceNode(nodeID, replacement.id)); + }, graph); + // Process any relations too - var parentRels = updatedWaysGraph.parentRelations(node); - return parentRels - .reduce(function (accGraph, parentRel) { - // Move the relationship to the new node - var originalMember = parentRel.memberById(nodeId); - var newMember = { id: replacementNode.id, type: 'node', role: originalMember.role }; - // Remove & replace with the new member - var updatedRel = parentRel.removeMembersWithID(nodeId) - .addMember(newMember, originalMember.index); - // Update graph and pass into the next cycle of the reduce operation - return accGraph.replace(updatedRel); - }, updatedWaysGraph); + return graph.parentRelations(node) + .reduce(function(accGraph, parentRel) { + return accGraph.replace(parentRel.replaceMember(node, replacement)); + }, graph); }; + + + action.disabled = function(graph) { + var node = graph.entity(nodeID); + var parentRels = graph.parentRelations(node); + + for (var i = 0; i < parentRels.length; i++) { + var relation = parentRels[i]; + if (!relation.isValidRestriction()) continue; + + for (var j = 0; j < relation.members.length; j++) { + var m = relation.members[j]; + if (m.id === nodeID && (m.role === 'via' || m.role === 'location_hint')) { + return 'restriction'; + } + } + } + + return false; + }; + + + return action; } diff --git a/modules/core/graph.js b/modules/core/graph.js index bfe2c1cf2..31c06c410 100644 --- a/modules/core/graph.js +++ b/modules/core/graph.js @@ -52,9 +52,8 @@ coreGraph.prototype = { transient: function(entity, key, fn) { - var id = entity.id, - transients = this.transients[id] || - (this.transients[id] = {}); + var id = entity.id; + var transients = this.transients[id] || (this.transients[id] = {}); if (transients[key] !== undefined) { return transients[key]; @@ -67,8 +66,8 @@ coreGraph.prototype = { parentWays: function(entity) { - var parents = this._parentWays[entity.id], - result = []; + var parents = this._parentWays[entity.id]; + var result = []; if (parents) { for (var i = 0; i < parents.length; i++) { @@ -92,8 +91,8 @@ coreGraph.prototype = { parentRelations: function(entity) { - var parents = this._parentRels[entity.id], - result = []; + var parents = this._parentRels[entity.id]; + var result = []; if (parents) { for (var i = 0; i < parents.length; i++) { @@ -134,8 +133,8 @@ coreGraph.prototype = { // data into each state. To external consumers, it should appear as if the // graph always contained the newly downloaded data. rebase: function(entities, stack, force) { - var base = this.base(), - i, j, k, id; + var base = this.base(); + var i, j, k, id; for (i = 0; i < entities.length; i++) { var entity = entities[i]; @@ -168,8 +167,8 @@ coreGraph.prototype = { _updateRebased: function() { - var base = this.base(), - i, k, child, id, keys; + var base = this.base(); + var i, k, child, id, keys; keys = Object.keys(this._parentWays); for (i = 0; i < keys.length; i++) { @@ -206,17 +205,13 @@ coreGraph.prototype = { // Updates calculated properties (parentWays, parentRels) for the specified change _updateCalculated: function(oldentity, entity, parentWays, parentRels) { - parentWays = parentWays || this._parentWays; parentRels = parentRels || this._parentRels; - var type = entity && entity.type || oldentity && oldentity.type, - removed, added, ways, rels, i; + var type = entity && entity.type || oldentity && oldentity.type; + var removed, added, ways, rels, i; - - if (type === 'way') { - - // Update parentWays + if (type === 'way') { // Update parentWays if (oldentity && entity) { removed = _difference(oldentity.nodes, entity.nodes); added = _difference(entity.nodes, oldentity.nodes); @@ -236,9 +231,7 @@ coreGraph.prototype = { parentWays[added[i]] = ways; } - } else if (type === 'relation') { - - // Update parentRels + } else if (type === 'relation') { // Update parentRels if (oldentity && entity) { removed = _difference(oldentity.members, entity.members); added = _difference(entity.members, oldentity); @@ -262,8 +255,7 @@ coreGraph.prototype = { replace: function(entity) { - if (this.entities[entity.id] === entity) - return this; + if (this.entities[entity.id] === entity) return this; return this.update(function() { this._updateCalculated(this.entities[entity.id], entity); @@ -281,11 +273,9 @@ coreGraph.prototype = { revert: function(id) { - var baseEntity = this.base().entities[id], - headEntity = this.entities[id]; - - if (headEntity === baseEntity) - return this; + var baseEntity = this.base().entities[id]; + var headEntity = this.entities[id]; + if (headEntity === baseEntity) return this; return this.update(function() { this._updateCalculated(headEntity, baseEntity); @@ -296,7 +286,6 @@ coreGraph.prototype = { update: function() { var graph = this.frozen ? coreGraph(this, true) : this; - for (var i = 0; i < arguments.length; i++) { arguments[i].call(graph, graph); } diff --git a/modules/operations/detach_node.js b/modules/operations/detach_node.js index bb1f985cf..268b8554e 100644 --- a/modules/operations/detach_node.js +++ b/modules/operations/detach_node.js @@ -1,104 +1,85 @@ -import { actionDetachNode } from '../actions/index'; -import { behaviorOperation } from '../behavior/index'; -import { modeMove } from '../modes/index'; +import _some from 'lodash-es/some'; + +import { actionDetachNode, actionMoveNode } from '../actions'; +import { behaviorOperation } from '../behavior'; +import { modeMove } from '../modes'; import { t } from '../util/locale'; -import _flatMap from 'lodash-es/flatMap'; -import _uniq from 'lodash-es/uniq'; + export function operationDetachNode(selectedIDs, context) { - var selectedNode = selectedIDs[0]; + var nodeID = selectedIDs.length && selectedIDs[0]; + var action = actionDetachNode(nodeID); + 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; + context.perform(action); // do the detach + + var mouse = context.map().mouseCoordinates(); + if (mouse.some(isNaN)) { + enterMoveMode(); + + } else { + // move detached node to the mouse location (transitioned) + context.perform(actionMoveNode(nodeID, mouse)); + + // after transition completes, put at final mouse location and enter move mode. + window.setTimeout(function() { + mouse = context.map().mouseCoordinates(); + context.replace(actionMoveNode(nodeID, mouse)); + enterMoveMode(); + }, 150); + } + + function enterMoveMode() { + var baseGraph = context.graph(); + context.enter(modeMove(context, [nodeID], baseGraph)); + } }; + + operation.available = function () { - // Check multiple items aren't selected - if (selectedIDs.length !== 1) { - return false; - } - // Get the entity itself + if (selectedIDs.length !== 1) return false; + 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; + var entity = graph.hasEntity(nodeID); + if (!entity) return false; + + return entity.type === 'node' && + entity.hasInterestingTags() && + graph.parentWays(entity).length > 0; }; + + operation.disabled = function () { - return false; + var reason; + if (_some(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; }; + + operation.tooltip = function () { var disableReason = operation.disabled(); - return disableReason - ? t('operations.detachNode.' + disableReason) - : t('operations.detachNode.description'); + if (disableReason) { + return t('operations.detach_node.' + disableReason, + { relation: context.presets().item('type/restriction').name() }); + } else { + return t('operations.detach_node.description'); + } }; + + operation.annotation = function () { - return t('operations.detachNode.annotation'); + return t('operations.detach_node.annotation'); }; - operation.id = 'detachNode'; - operation.keys = [t('operations.detachNode.key')]; - operation.title = t('operations.detachNode.title'); + + + operation.id = 'detach-node'; + operation.keys = [t('operations.detach_node.key')]; + operation.title = t('operations.detach_node.title'); operation.behavior = behaviorOperation(context).which(operation); - operation.disabled = function () { - // We should prevent the node being detached if it represents a via/location_hint node of a turn restriction - var graph = context.graph(); - // Get nodes for the Ids (although there should only be one, we can handle multiple here) - var nodes = selectedIDs.map(function (i) { return graph.hasEntity(i); }) - .filter(isNotNullOrUndefined); - // Get all via nodes of restrictions involving the target nodes - var restrictionNodeIds = _flatMap(nodes, function (node) { - // Get the relations that this node belongs to - var relationsFromNode = graph.parentRelations(node); - // Check each relation in turn - return _flatMap(relationsFromNode, function (relation) { - // Check to see if this is a restriction relation, if not return null - if (!relation.isValidRestriction()) { - return null; - } - // We have identified that it is a restriction. - // https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that - // from & to roles are only appropriate for Ways - // The via members can be either nodes or ways. Via-Ways do not prevent us removing a node - // from within them, as it is the way itself which is in the relation with the via role, - // and not the consitutent nodes (so if we switch out a constituent node, the way id - // does not change and therefore the relation will not be affected). Therefore we - // only need to examine the standalone nodes - return relation.members.filter(function (m) { - return (m.role === 'via' || m.role === 'location_hint') && m.type === 'node'; - }).map(function (m) { return m.id; }); - }); - }).filter(isNotNullOrUndefined); - // Get unique list of ids in restrictionNodeIds to simplify checking - var nodeIds = _uniq(restrictionNodeIds); - - // Now we have a list of via/location_hint nodes, we should prevent detachment if the target node is in this list - var anyInhibits = nodes.filter(function (n) { - return nodeIds.indexOf(n.id) !== -1; - }); - if (anyInhibits.length > 0) { - // The node is a via/location_hint, do not permit - return 'via_restriction'; - } - // We are ok to proceed - return false; - }; return operation; } -function isNotNullOrUndefined(i) { - return i !== undefined && i !== null; -} \ No newline at end of file diff --git a/modules/osm/relation.js b/modules/osm/relation.js index 8532990ed..1ba283df4 100644 --- a/modules/osm/relation.js +++ b/modules/osm/relation.js @@ -42,8 +42,7 @@ _extend(osmRelation.prototype, { copy: function(resolver, copies) { - if (copies[this.id]) - return copies[this.id]; + if (copies[this.id]) return copies[this.id]; var copy = osmEntity.prototype.copy.call(this, resolver, copies); diff --git a/modules/osm/way.js b/modules/osm/way.js index af1b3d476..c4da27c25 100644 --- a/modules/osm/way.js +++ b/modules/osm/way.js @@ -31,8 +31,7 @@ _extend(osmWay.prototype, { copy: function(resolver, copies) { - if (copies[this.id]) - return copies[this.id]; + if (copies[this.id]) return copies[this.id]; var copy = osmEntity.prototype.copy.call(this, resolver, copies); @@ -239,9 +238,9 @@ _extend(osmWay.prototype, { unclose: function() { if (!this.isClosed()) return this; - var nodes = this.nodes.slice(), - connector = this.first(), - i = nodes.length - 1; + var nodes = this.nodes.slice(); + var connector = this.first(); + var i = nodes.length - 1; // remove trailing connectors.. while (i > 0 && nodes.length > 1 && nodes[i] === connector) { @@ -260,9 +259,9 @@ _extend(osmWay.prototype, { // Consecutive duplicates are eliminated including existing ones. // Circularity is always preserved when adding a node. addNode: function(id, index) { - var nodes = this.nodes.slice(), - isClosed = this.isClosed(), - max = isClosed ? nodes.length - 1 : nodes.length; + var nodes = this.nodes.slice(); + var isClosed = this.isClosed(); + var max = isClosed ? nodes.length - 1 : nodes.length; if (index === undefined) { index = max; @@ -309,9 +308,9 @@ _extend(osmWay.prototype, { // Consecutive duplicates are eliminated including existing ones. // Circularity is preserved when updating a node. updateNode: function(id, index) { - var nodes = this.nodes.slice(), - isClosed = this.isClosed(), - max = nodes.length - 1; + var nodes = this.nodes.slice(); + var isClosed = this.isClosed(); + var max = nodes.length - 1; if (index === undefined || index < 0 || index > max) { throw new RangeError('index ' + index + ' out of range 0..' + max); @@ -353,13 +352,13 @@ _extend(osmWay.prototype, { // Replaces each occurrence of node id needle with replacement. // Consecutive duplicates are eliminated including existing ones. // Circularity is preserved. - replaceNode: function(needle, replacement) { - var nodes = this.nodes.slice(), - isClosed = this.isClosed(); + replaceNode: function(needleID, replacementID) { + var nodes = this.nodes.slice(); + var isClosed = this.isClosed(); for (var i = 0; i < nodes.length; i++) { - if (nodes[i] === needle) { - nodes[i] = replacement; + if (nodes[i] === needleID) { + nodes[i] = replacementID; } } @@ -374,12 +373,12 @@ _extend(osmWay.prototype, { }, - // Removes each occurrence of node id needle with replacement. + // Removes each occurrence of node id. // Consecutive duplicates are eliminated including existing ones. // Circularity is preserved. removeNode: function(id) { - var nodes = this.nodes.slice(), - isClosed = this.isClosed(); + var nodes = this.nodes.slice(); + var isClosed = this.isClosed(); nodes = nodes .filter(function(node) { return node !== id; }) diff --git a/svg/iD-sprite/operations/operation-detach-node.svg b/svg/iD-sprite/operations/operation-detach-node.svg new file mode 100644 index 000000000..a10edfc66 --- /dev/null +++ b/svg/iD-sprite/operations/operation-detach-node.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/svg/iD-sprite/operations/operation-detachNode.svg b/svg/iD-sprite/operations/operation-detachNode.svg deleted file mode 100644 index 504acc2cb..000000000 --- a/svg/iD-sprite/operations/operation-detachNode.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/test/spec/actions/detach_node.js b/test/spec/actions/detach_node.js index 16fcc5e3a..1bac58e44 100644 --- a/test/spec/actions/detach_node.js +++ b/test/spec/actions/detach_node.js @@ -1,20 +1,22 @@ describe('iD.actionDetachNode', function () { var tags = { 'name': 'test' }; + function createTargetNode(id, lonlat) { - return iD.Node({ id: id, loc: lonlat, tags: tags }); + return iD.osmNode({ id: id, loc: lonlat, tags: tags }); } - describe('simple way', function () { + + describe('linear 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'] }) + // + // a -- b -- c -- d + // + graph = iD.coreGraph([ + iD.osmNode({ id: 'a', loc: [0, 0] }), + iD.osmNode({ id: 'b', loc: [1, 0] }), + iD.osmNode({ id: 'c', loc: [2, 0] }), + iD.osmNode({ id: 'd', loc: [3, 0] }), + iD.osmWay({ id: '-', nodes: ['a', 'b', 'c', 'd'] }) ]); }); @@ -30,7 +32,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the way still has 4 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(4); }); @@ -39,11 +41,11 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); @@ -54,7 +56,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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]); @@ -65,7 +67,7 @@ describe('iD.actionDetachNode', function () { // Act var assertionGraph = iD.actionDetachNode('a')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').nodes; // Confirm that the target is no longer "a" expect(nodes[0]).not.to.eql('a'); // and that the tags are not present @@ -100,7 +102,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the way still has 4 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(4); }); @@ -109,11 +111,11 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); @@ -124,7 +126,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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]); @@ -135,7 +137,7 @@ describe('iD.actionDetachNode', function () { // Act var assertionGraph = iD.actionDetachNode('b')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').nodes; // Confirm that the target is no longer "a" expect(nodes[1]).not.to.eql('b'); // and that the tags are not present @@ -158,19 +160,22 @@ describe('iD.actionDetachNode', function () { }); }); }); + + 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'] }) + // + // d -- c + // | | + // a -- b + // + graph = iD.coreGraph([ + iD.osmNode({ id: 'a', loc: [0, 0] }), + iD.osmNode({ id: 'b', loc: [1, 0] }), + iD.osmNode({ id: 'c', loc: [1, 1] }), + iD.osmNode({ id: 'd', loc: [0, 1] }), + iD.osmWay({ id: '-', nodes: ['a', 'b', 'c', 'd', 'a'] }) ]); }); @@ -186,7 +191,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the way still has 5 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(5); }); @@ -195,11 +200,11 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); @@ -212,7 +217,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('a')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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]); @@ -224,7 +229,7 @@ describe('iD.actionDetachNode', function () { // Act var assertionGraph = iD.actionDetachNode('a')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').nodes; // Confirm that the target is no longer "a" expect(nodes[0]).not.to.eql('a'); // .. also in the tail position @@ -261,7 +266,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the way still has 5 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(5); }); @@ -270,11 +275,11 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); @@ -286,7 +291,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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]); @@ -298,7 +303,7 @@ describe('iD.actionDetachNode', function () { // Act var assertionGraph = iD.actionDetachNode('b')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').nodes; // Confirm that the target is no longer "a" expect(nodes[1]).not.to.eql('b'); // and that the tags are not present @@ -321,23 +326,29 @@ describe('iD.actionDetachNode', function () { }); }); }); - describe('intersecting simple ways', function () { + + + describe('intersecting linear 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) + // + // f + // ‖ + // e + // ‖ + // a -- b -- c -- d + // // 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'] }) + // + graph = iD.coreGraph([ + iD.osmNode({ id: 'a', loc: [0, 0] }), + iD.osmNode({ id: 'b', loc: [1, 0] }), + iD.osmNode({ id: 'c', loc: [2, 0], tags: tags }), + iD.osmNode({ id: 'd', loc: [3, 0] }), + iD.osmNode({ id: 'e', loc: [2, 1] }), + iD.osmNode({ id: 'f', loc: [2, 2] }), + iD.osmWay({ id: '-', nodes: ['a', 'b', 'c', 'd'] }), + iD.osmWay({ id: '=', nodes: ['c', 'e', 'f'] }) ]); }); @@ -346,10 +357,10 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the way still has 4 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(4); // .. and second way has 3 - target = assertionGraph.entity('x'); + target = assertionGraph.entity('='); expect(target.nodes.length).to.eql(3); }); @@ -358,16 +369,16 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); + target = assertionGraph.entity('='); expect(target.nodes[1]).to.eql('e'); expect(target.nodes[2]).to.eql('f'); }); @@ -377,13 +388,13 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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; + nodes = assertionGraph.entity('=').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]); @@ -393,20 +404,20 @@ describe('iD.actionDetachNode', 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]); + expect(assertionGraph.entity('-').nodes[2]).to.eql(assertionGraph.entity('=').nodes[0]); }); it('does replace target node', function () { // Act var assertionGraph = iD.actionDetachNode('c')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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]); + expect(assertionGraph.entity('=').nodes[0]).to.eql(nodes[2]); }); it('does detach target node', function () { @@ -424,26 +435,30 @@ describe('iD.actionDetachNode', function () { 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'] }) + // + // g == f + // ‖ ‖ + // d -- c == e + // | | + // a -- b + // + // c is the target node + // + graph = iD.coreGraph([ + iD.osmNode({ id: 'a', loc: [0, 0] }), + iD.osmNode({ id: 'b', loc: [1, 0] }), + iD.osmNode({ id: 'c', loc: [1, 1], tags: tags }), + iD.osmNode({ id: 'd', loc: [0, 1] }), + iD.osmNode({ id: 'e', loc: [2, 1] }), + iD.osmNode({ id: 'f', loc: [2, 2] }), + iD.osmNode({ id: 'g', loc: [1, 2] }), + iD.osmWay({ id: '-', nodes: ['a', 'b', 'c', 'd', 'a'] }), + iD.osmWay({ id: '=', nodes: ['c', 'e', 'f', 'g', 'c'] }) ]); }); @@ -452,10 +467,10 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the way still has 5 nodes - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); expect(target.nodes.length).to.eql(5); // and the second - target = assertionGraph.entity('x'); + target = assertionGraph.entity('='); expect(target.nodes.length).to.eql(5); }); @@ -464,18 +479,18 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the way is ordered correctly - var target = assertionGraph.entity('w'); + var target = assertionGraph.entity('-'); // 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 + // 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'); + target = assertionGraph.entity('='); expect(target.nodes[1]).to.eql('e'); expect(target.nodes[2]).to.eql('f'); expect(target.nodes[3]).to.eql('g'); @@ -487,32 +502,32 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('c')(graph); // Confirm that the nodes have not moved, including the replacement node - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').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; + nodes = assertionGraph.entity('=').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]); + expect(assertionGraph.entity(nodes[2]).loc).to.eql([2, 2]); + expect(assertionGraph.entity(nodes[3]).loc).to.eql([1, 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]); + expect(assertionGraph.entity('-').nodes[2]).to.eql(assertionGraph.entity('=').nodes[0]); }); it('does replace target node', function () { // Act var assertionGraph = iD.actionDetachNode('c')(graph); - var nodes = assertionGraph.entity('w').nodes; + var nodes = assertionGraph.entity('-').nodes; // Confirm that the target is no longer "c" expect(nodes[0]).not.to.eql('c'); // .. also in the tail position @@ -537,25 +552,24 @@ describe('iD.actionDetachNode', function () { expect(assertionGraph.parentWays(targetNode)).to.eql([]); }); }); + + describe('with relation', function () { var graph; beforeEach(function () { - // Set up a simple way - // a-b-c (0,0)-(1,0)-(2,0) + // + // a -- b -- c + // // Node b represents the target // With a relationship for the way including b - graph = iD.Graph([ - iD.Node({ id: 'a', loc: [0, 0] }), - iD.Node({ id: 'b', loc: [1, 0], tags: tags }), - iD.Node({ id: 'c', loc: [2, 0] }), - iD.Way({ id: 'w', nodes: ['a', 'b', 'c'] }), - iD.Relation({ - id: 'r', - tags: { - type: 'route', - route: 'foot' - }, + // + graph = iD.coreGraph([ + iD.osmNode({ id: 'a', loc: [0, 0] }), + iD.osmNode({ id: 'b', loc: [1, 0], tags: tags }), + iD.osmNode({ id: 'c', loc: [2, 0] }), + iD.osmWay({ id: '-', nodes: ['a', 'b', 'c'] }), + iD.osmRelation({id: 'r', tags: {type: 'route', route: 'foot'}, members: [ { id: 'a', type: 'node', role: 'point' }, { id: 'b', type: 'node', role: 'point' }, @@ -577,7 +591,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Find the new node - var targetWay = assertionGraph.entity('w'); + var targetWay = assertionGraph.entity('-'); var newNodeId = targetWay.nodes.filter(function (m) { return m !== 'a' && m !== 'b' && m !== 'c'; })[0]; @@ -592,7 +606,7 @@ describe('iD.actionDetachNode', function () { var assertionGraph = iD.actionDetachNode('b')(graph); // Find the new node - var targetWay = assertionGraph.entity('w'); + var targetWay = assertionGraph.entity('-'); var newNodeId = targetWay.nodes.filter(function (m) { return m !== 'a' && m !== 'b' && m !== 'c'; })[0]; @@ -607,4 +621,4 @@ describe('iD.actionDetachNode', function () { }); }); -}); \ No newline at end of file +}); diff --git a/test/spec/operations/detach_node.js b/test/spec/operations/detach_node.js index 357d390fa..e79bf8349 100644 --- a/test/spec/operations/detach_node.js +++ b/test/spec/operations/detach_node.js @@ -2,13 +2,13 @@ describe('iD.operationDetachNode', function () { var fakeContext; var graph; - // Some common setup functions // Set up the fake context fakeContext = {}; - fakeContext.graph = function () { - return graph; - }; + fakeContext.graph = function () { return graph; }; + fakeContext.hasHiddenConnections = function () { return false; }; + var fakeTags = { 'name': 'fake' }; + // Set up graph var createFakeNode = function (id, hasTags) { return hasTags @@ -24,112 +24,108 @@ describe('iD.operationDetachNode', function () { // d - node with no tags, 2 parent ways // e - node with tags, no parent way // f - node with no tags, no parent way - graph = iD.Graph([ - iD.Node(createFakeNode('a', true)), - iD.Node(createFakeNode('b', true)), - iD.Node(createFakeNode('c', false)), - iD.Node(createFakeNode('d', false)), - iD.Node(createFakeNode('e', true)), - iD.Node(createFakeNode('f', false)), - iD.Way({ id: 'x', nodes: ['a', 'b', 'c', 'd'] }), - iD.Way({ id: 'y', nodes: ['b', 'd'] }) + graph = iD.coreGraph([ + iD.osmNode(createFakeNode('a', true)), + iD.osmNode(createFakeNode('b', true)), + iD.osmNode(createFakeNode('c', false)), + iD.osmNode(createFakeNode('d', false)), + iD.osmNode(createFakeNode('e', true)), + iD.osmNode(createFakeNode('f', false)), + iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c', 'd'] }), + iD.osmWay({ id: 'y', nodes: ['b', 'd'] }) ]); }); it('is not available for no selected ids', function () { var result = iD.operationDetachNode([], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for two selected ids', function () { var result = iD.operationDetachNode(['a', 'b'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); - it('is not available for unkown selected id', function () { + it('is not available for unknown selected id', function () { var result = iD.operationDetachNode(['z'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for selected way', function () { var result = iD.operationDetachNode(['x'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for selected node with tags, no parent way', function () { var result = iD.operationDetachNode(['e'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, no parent way', function () { var result = iD.operationDetachNode(['f'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, parent way', function () { var result = iD.operationDetachNode(['c'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, two parent ways', function () { var result = iD.operationDetachNode(['d'], fakeContext).available(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('is available for selected node with tags, parent way', function () { var result = iD.operationDetachNode(['a'], fakeContext).available(); - expect(result).to.eql(true); + expect(result).to.be.ok; }); it('is available for selected node with tags, two parent ways', function () { var result = iD.operationDetachNode(['b'], fakeContext).available(); - expect(result).to.eql(true); + expect(result).to.be.ok; }); }); + describe('disabled', function () { it('returns enabled for non-related node', function () { - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', true)), - iD.Node(createFakeNode('c', false)), - iD.Way({ id: 'x', nodes: ['a', 'b', 'c'] }) + graph = iD.coreGraph([ + iD.osmNode(createFakeNode('a', false)), + iD.osmNode(createFakeNode('b', true)), + iD.osmNode(createFakeNode('c', false)), + iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c'] }) ]); var result = iD.operationDetachNode(['b'], fakeContext).disabled(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('returns enabled for non-restriction related node', function () { - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', true)), - iD.Node(createFakeNode('c', false)), - iD.Way({ id: 'x', nodes: ['a', 'b', 'c'] }), - iD.Relation({ id: 'r', members: [{ id: 'b', role: 'label' }] }) + graph = iD.coreGraph([ + iD.osmNode(createFakeNode('a', false)), + iD.osmNode(createFakeNode('b', true)), + iD.osmNode(createFakeNode('c', false)), + iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c'] }), + iD.osmRelation({ id: 'r', members: [{ id: 'b', role: 'label' }] }) ]); var result = iD.operationDetachNode(['b'], fakeContext).disabled(); - expect(result).to.eql(false); + expect(result).to.be.not.ok; }); it('returns not-enabled for via node in restriction', function () { // https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that // from & to roles are only appropriate for Ways - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', false)), - iD.Node(createFakeNode('c', false)), - iD.Node(createFakeNode('d', true)), - iD.Node(createFakeNode('e', false)), - iD.Node(createFakeNode('f', false)), - iD.Node(createFakeNode('g', false)), - iD.Way({ id: 'x', nodes: ['a', 'b', 'c'] }), - iD.Way({ id: 'y', nodes: ['e', 'f', 'g'] }), - iD.Relation({ - id: 'r', - tags: { - type: 'restriction', - restriction: 'no_right_turn' - }, + graph = iD.coreGraph([ + iD.osmNode(createFakeNode('a', false)), + iD.osmNode(createFakeNode('b', false)), + iD.osmNode(createFakeNode('c', false)), + iD.osmNode(createFakeNode('d', true)), + iD.osmNode(createFakeNode('e', false)), + iD.osmNode(createFakeNode('f', false)), + iD.osmNode(createFakeNode('g', false)), + iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c'] }), + iD.osmWay({ id: 'y', nodes: ['e', 'f', 'g'] }), + iD.osmRelation({id: 'r', tags: {type: 'restriction', restriction: 'no_right_turn'}, members: [ { id: 'x', type: 'way', role: 'from' }, { id: 'd', type: 'node', role: 'via' }, @@ -138,63 +134,23 @@ describe('iD.operationDetachNode', function () { }) ]); var result = iD.operationDetachNode(['d'], fakeContext).disabled(); - expect(result).not.to.eql(false); - }); - - it('returns not-enabled for via node in restriction and other non-restriction relation', function () { - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', false)), - iD.Node(createFakeNode('c', false)), - iD.Node(createFakeNode('d', true)), - iD.Node(createFakeNode('e', false)), - iD.Node(createFakeNode('f', false)), - iD.Node(createFakeNode('g', false)), - iD.Way({ id: 'x', nodes: ['a', 'b', 'c'] }), - iD.Way({ id: 'y', nodes: ['e', 'f', 'g'] }), - iD.Relation({ - id: 'r', - tags: { - type: 'restriction', - restriction: 'no_right_turn' - }, - members: [ - { id: 'x', type: 'way', role: 'from' }, - { id: 'd', type: 'node', role: 'via' }, - { id: 'z', type: 'way', role: 'to' } - ] - }), - iD.Relation({ - id: 's', - members: [ - { id: 'x', type: 'way' }, - { id: 'd', type: 'node' }, - ] - }) - ]); - var result = iD.operationDetachNode(['d'], fakeContext).disabled(); - expect(result).not.to.eql(false); + expect(result).to.eql('restriction'); }); it('returns not-enabled for location_hint node in restriction', function () { // https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that // from & to roles are only appropriate for Ways - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', false)), - iD.Node(createFakeNode('c', false)), - iD.Node(createFakeNode('d', true)), - iD.Node(createFakeNode('e', false)), - iD.Node(createFakeNode('f', false)), - iD.Node(createFakeNode('g', false)), - iD.Way({ id: 'x', nodes: ['a', 'b'] }), - iD.Way({ id: 'y', nodes: ['e', 'f', 'g'] }), - iD.Relation({ - id: 'r', - tags: { - type: 'restriction', - restriction: 'no_right_turn' - }, + graph = iD.coreGraph([ + iD.osmNode(createFakeNode('a', false)), + iD.osmNode(createFakeNode('b', false)), + iD.osmNode(createFakeNode('c', false)), + iD.osmNode(createFakeNode('d', true)), + iD.osmNode(createFakeNode('e', false)), + iD.osmNode(createFakeNode('f', false)), + iD.osmNode(createFakeNode('g', false)), + iD.osmWay({ id: 'x', nodes: ['a', 'b'] }), + iD.osmWay({ id: 'y', nodes: ['e', 'f', 'g'] }), + iD.osmRelation({id: 'r', tags: {type: 'restriction', restriction: 'no_right_turn'}, members: [ { id: 'x', type: 'way', role: 'from' }, { id: 'c', type: 'node', role: 'via' }, @@ -204,43 +160,7 @@ describe('iD.operationDetachNode', function () { }) ]); var result = iD.operationDetachNode(['d'], fakeContext).disabled(); - expect(result).not.to.eql(false); - }); - - it('returns not-enabled for location_hint node in restriction and other non-restriction relation', function () { - graph = iD.Graph([ - iD.Node(createFakeNode('a', false)), - iD.Node(createFakeNode('b', false)), - iD.Node(createFakeNode('c', false)), - iD.Node(createFakeNode('d', true)), - iD.Node(createFakeNode('e', false)), - iD.Node(createFakeNode('f', false)), - iD.Node(createFakeNode('g', false)), - iD.Way({ id: 'x', nodes: ['a', 'b'] }), - iD.Way({ id: 'y', nodes: ['e', 'f', 'g'] }), - iD.Relation({ - id: 'r', - tags: { - type: 'restriction', - restriction: 'no_right_turn' - }, - members: [ - { id: 'x', type: 'way', role: 'from' }, - { id: 'c', type: 'node', role: 'via' }, - { id: 'd', type: 'node', role: 'location_hint' }, - { id: 'z', type: 'way', role: 'to' } - ] - }), - iD.Relation({ - id: 's', - members: [ - { id: 'x', type: 'way' }, - { id: 'd', type: 'node' }, - ] - }) - ]); - var result = iD.operationDetachNode(['d'], fakeContext).disabled(); - expect(result).not.to.eql(false); + expect(result).to.eql('restriction'); }); }); });