diff --git a/Makefile b/Makefile index c9d13ff60..70ac208d6 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,8 @@ all: \ js/id/behavior/*.js \ js/id/modes.js \ js/id/modes/*.js \ + js/id/operations.js \ + js/id/operations/*.js \ js/id/controller/*.js \ js/id/graph/*.js \ js/id/renderer/*.js \ diff --git a/css/map.css b/css/map.css index 8f235bb18..7b3038d6c 100644 --- a/css/map.css +++ b/css/map.css @@ -16,7 +16,7 @@ g.point .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.point.hover .shadow { +.behavior-hover g.point.hover .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -106,7 +106,7 @@ g.vertex .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.vertex.hover .shadow { +.behavior-hover g.vertex.hover .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -120,7 +120,7 @@ g.vertex.selected .shadow { g.midpoint .fill { fill:#aaa; } -g.midpoint .fill.hover { +.behavior-hover g.midpoint .fill.hover { fill:#fff; stroke:#000; } @@ -133,7 +133,7 @@ g.midpoint .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.midpoint .shadow.hover { +.behavior-hover g.midpoint .shadow.hover { fill:#E96666; fill-opacity: 0.3; } @@ -161,7 +161,7 @@ path.shadow { -webkit-transition: stroke 100ms linear; } -path.shadow.hover { +.behavior-hover path.shadow.hover { stroke: #E96666; stroke-opacity: 0.3; } @@ -691,17 +691,17 @@ text.point.tag-amenity { cursor:url(../img/cursor-draw.png) 9 9, auto; } -.mode-draw-line .way, -.mode-draw-area .way, -.mode-add-line .way, -.mode-add-area .way { +.mode-draw-line .behavior-hover .way, +.mode-draw-area .behavior-hover .way, +.mode-add-line .behavior-hover .way, +.mode-add-area .behavior-hover .way { cursor:url(../img/cursor-draw-connect-line.png) 9 9, auto; } -.mode-draw-line .vertex, -.mode-draw-area .vertex, -.mode-add-line .vertex, -.mode-add-area .vertex { +.mode-draw-line .behavior-hover .vertex, +.mode-draw-area .behavior-hover .vertex, +.mode-add-line .behavior-hover .vertex, +.mode-add-area .behavior-hover .vertex { cursor:url(../img/cursor-draw-connect-vertex.png) 9 9, auto; } diff --git a/index.html b/index.html index af436a86e..deadb5add 100644 --- a/index.html +++ b/index.html @@ -92,7 +92,6 @@ - @@ -104,8 +103,17 @@ + + + + + + + + + diff --git a/js/id/actions/circular.js b/js/id/actions/circular.js index d6af08560..da7965c79 100644 --- a/js/id/actions/circular.js +++ b/js/id/actions/circular.js @@ -51,7 +51,7 @@ iD.actions.Circular = function(wayId, map) { }; action.enabled = function(graph) { - return true; + return graph.entity(wayId).isClosed(); }; return action; diff --git a/js/id/actions/delete_node.js b/js/id/actions/delete_node.js index 48e704ac3..a93129ce3 100644 --- a/js/id/actions/delete_node.js +++ b/js/id/actions/delete_node.js @@ -1,6 +1,6 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as iD.actions.DeleteNode = function(nodeId) { - var action = function(graph) { + return function(graph) { var node = graph.entity(nodeId); graph.parentWays(node) @@ -20,10 +20,4 @@ iD.actions.DeleteNode = function(nodeId) { return graph.remove(node); }; - - action.enabled = function(graph) { - return true; - }; - - return action; }; diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index eb58223cb..211447228 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -1,6 +1,6 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as iD.actions.DeleteWay = function(wayId) { - var action = function(graph) { + return function(graph) { var way = graph.entity(wayId); graph.parentRelations(way) @@ -27,10 +27,4 @@ iD.actions.DeleteWay = function(wayId) { return graph.remove(way); }; - - action.enabled = function(graph) { - return true; - }; - - return action; }; diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse_way.js index 4a7d392b7..160017637 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse_way.js @@ -53,7 +53,7 @@ iD.actions.ReverseWay = function(wayId) { } } - var action = function(graph) { + return function(graph) { var way = graph.entity(wayId), nodes = way.nodes.slice().reverse(), tags = {}, key, role; @@ -73,10 +73,4 @@ iD.actions.ReverseWay = function(wayId) { return graph.replace(way.update({nodes: nodes, tags: tags})); }; - - action.enabled = function(graph) { - return true; - }; - - return action; }; diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 7c02355fb..021377890 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -3,11 +3,9 @@ iD.behavior.AddWay = function(mode) { history = mode.history, controller = mode.controller, event = d3.dispatch('startFromNode', 'startFromWay', 'start'), - hover, draw; - - function add() { - var datum = d3.select(d3.event.target).datum() || {}; + draw; + function add(datum) { if (datum.type === 'node') { event.startFromNode(datum); } else if (datum.type === 'way') { @@ -26,8 +24,7 @@ iD.behavior.AddWay = function(mode) { .minzoom(16) .dblclickEnable(false); - surface.call(hover) - .call(draw); + surface.call(draw); }; addWay.off = function(surface) { @@ -39,16 +36,13 @@ iD.behavior.AddWay = function(mode) { map.dblclickEnable(true); }, 1000); - surface.call(hover.off) - .call(draw.off); + surface.call(draw.off); }; addWay.cancel = function() { controller.exit(); }; - hover = iD.behavior.Hover(); - draw = iD.behavior.Draw() .on('add', add) .on('cancel', addWay.cancel) diff --git a/js/id/behavior/drag_way.js b/js/id/behavior/drag_way.js deleted file mode 100644 index 6de8ca252..000000000 --- a/js/id/behavior/drag_way.js +++ /dev/null @@ -1,28 +0,0 @@ -iD.behavior.DragWay = function(mode) { - var history = mode.history, - projection = mode.map.projection; - - return iD.behavior.drag() - .delegate('.casing, .stroke, .area') - .filter(function(d) { - return d && d.id === mode.entity.id; - }) - .origin(function(entity) { - return projection(history.graph().entity(entity.nodes[0]).loc); - }) - .on('start', function() { - history.perform( - iD.actions.Noop()); - }) - .on('move', function(entity) { - d3.event.sourceEvent.stopPropagation(); - history.replace( - iD.actions.MoveWay(entity.id, d3.event.delta, projection), - 'moved a way'); - }) - .on('end', function() { - history.replace( - iD.actions.Noop(), - 'moved a way'); - }); -}; diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 2725b8111..4d0153505 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,45 +1,64 @@ iD.behavior.Draw = function () { var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - down; + down, surface, hover; + + function datum() { + if (d3.event.altKey) { + return {}; + } else { + return d3.event.target.__data__ || {}; + } + } + + function mousedown() { + down = true; + } + + function mouseup() { + down = false; + } + + function mousemove() { + if (!down) { + event.move(datum()); + } + } + + function click() { + event.add(datum()); + } + + function keydown() { + if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { + surface.call(hover.off); + } + } + + function keyup() { + if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { + surface.call(hover); + } + } + + function backspace() { + d3.event.preventDefault(); + event.undo(); + } + + function del() { + d3.event.preventDefault(); + event.cancel(); + } + + function ret() { + d3.event.preventDefault(); + event.finish(); + } function draw(selection) { - function mousemove() { - if (!down) event.move(); - } - - function click() { - event.add(); - } - - function mousedown() { - down = true; - } - - function mouseup() { - down = false; - } - - function backspace() { - d3.event.preventDefault(); - event.undo(); - } - - function del() { - d3.event.preventDefault(); - event.cancel(); - } - - function ret() { - d3.event.preventDefault(); - event.finish(); - } - - selection - .on('mousedown.draw', mousedown) - .on('mouseup.draw', mouseup) - .on('mousemove.draw', mousemove) - .on('click.draw', click); + surface = selection; + hover = iD.behavior.Hover(); keybinding .on('⌫', backspace) @@ -47,18 +66,33 @@ iD.behavior.Draw = function () { .on('⎋', ret) .on('↩', ret); + selection + .on('mousedown.draw', mousedown) + .on('mouseup.draw', mouseup) + .on('mousemove.draw', mousemove) + .on('click.draw', click) + .call(hover); + d3.select(document) - .call(keybinding); + .call(keybinding) + .on('keydown.draw', keydown) + .on('keyup.draw', keyup); return draw; } draw.off = function(selection) { selection + .on('mousedown.draw', null) + .on('mouseup.draw', null) .on('mousemove.draw', null) - .on('click.draw', null); + .on('click.draw', null) + .call(hover.off); - keybinding.off(); + d3.select(document) + .call(keybinding.off) + .on('keydown.draw', null) + .on('keyup.draw', null); }; return d3.rebind(draw, event, 'on'); diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 0e5bcf4fd..63526df7a 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -5,7 +5,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'), way = mode.history.graph().entity(wayId), finished = false, - hover, draw; + draw; var node = iD.Node({loc: map.mouseCoordinates()}), nodeId = node.id; @@ -14,13 +14,19 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { iD.actions.AddNode(node), iD.actions.AddWayNode(wayId, node.id, index)); - function move() { - history.replace(iD.actions.MoveNode(nodeId, map.mouseCoordinates())); + function move(datum) { + var loc = map.mouseCoordinates(); + + if (datum.type === 'node' || datum.midpoint) { + loc = datum.loc; + } else if (datum.type === 'way') { + loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; + } + + history.replace(iD.actions.MoveNode(nodeId, loc)); } - function add() { - var datum = d3.select(d3.event.target).datum() || {}; - + function add(datum) { if (datum.id === headId) { event.addHead(datum); } else if (datum.id === tailId) { @@ -47,8 +53,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { .minzoom(16) .dblclickEnable(false); - surface.call(hover) - .call(draw) + surface.call(draw) .selectAll('.way, .node') .filter(function (d) { return d.id === wayId || d.id === nodeId; }) .classed('active', true); @@ -68,8 +73,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { map.dblclickEnable(true); }, 1000); - surface.call(hover.off) - .call(draw.off) + surface.call(draw.off) .selectAll('.way, .node') .classed('active', false); @@ -145,8 +149,6 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { controller.enter(iD.modes.Browse()); }; - hover = iD.behavior.Hover(); - draw = iD.behavior.Draw() .on('move', move) .on('add', add) diff --git a/js/id/behavior/hover.js b/js/id/behavior/hover.js index 9d50cbbd0..e4d33dd98 100644 --- a/js/id/behavior/hover.js +++ b/js/id/behavior/hover.js @@ -9,6 +9,8 @@ */ iD.behavior.Hover = function () { var hover = function(selection) { + selection.classed('behavior-hover', true); + selection.on('mouseover.hover', function () { var datum = d3.event.target.__data__; if (datum) { @@ -25,7 +27,8 @@ iD.behavior.Hover = function () { }; hover.off = function(selection) { - selection.on('mouseover.hover', null) + selection.classed('behavior-hover', false) + .on('mouseover.hover', null) .on('mouseout.hover', null); }; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js new file mode 100644 index 000000000..7fe07d1ce --- /dev/null +++ b/js/id/modes/move_way.js @@ -0,0 +1,69 @@ +iD.modes.MoveWay = function(wayId) { + var mode = { + id: 'move-way' + }; + + var keybinding = d3.keybinding('move-way'); + + mode.enter = function() { + var map = mode.map, + history = mode.history, + graph = history.graph(), + selection = map.surface, + controller = mode.controller, + projection = map.projection; + + var way = graph.entity(wayId), + origin = d3.mouse(selection.node()); + + history.perform( + iD.actions.Noop(), + 'moved a way'); + + function move() { + var p = d3.mouse(selection.node()), + delta = [p[0] - origin[0], + p[1] - origin[1]]; + + origin = p; + + history.replace( + iD.actions.MoveWay(wayId, delta, projection), + 'moved a way'); + } + + function finish() { + d3.event.stopPropagation(); + controller.enter(iD.modes.Select(way, true)); + } + + function cancel() { + history.pop(); + controller.enter(iD.modes.Select(way, true)); + } + + selection + .on('mousemove.move-way', move) + .on('click.move-way', finish); + + keybinding + .on('⎋', cancel) + .on('↩', finish); + + d3.select(document) + .call(keybinding); + }; + + mode.exit = function() { + var map = mode.map, + selection = map.surface; + + selection + .on('mousemove.move-way', null) + .on('click.move-way', null); + + keybinding.off(); + }; + + return mode; +}; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index fb4e5dc06..06c05b7ea 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -43,7 +43,6 @@ iD.modes.Select = function(entity, initial) { behaviors = [ iD.behavior.Hover(), iD.behavior.DragNode(mode), - iD.behavior.DragWay(mode), iD.behavior.DragMidpoint(mode)]; behaviors.forEach(function(behavior) { @@ -138,7 +137,7 @@ iD.modes.Select = function(entity, initial) { }) .classed('selected', true); - radialMenu = iD.ui.RadialMenu(entity, history, mode.map); + radialMenu = iD.ui.RadialMenu(entity, mode); if (d3.event && !initial) { var loc = map.mouseCoordinates(); diff --git a/js/id/operations.js b/js/id/operations.js new file mode 100644 index 000000000..2786d046f --- /dev/null +++ b/js/id/operations.js @@ -0,0 +1 @@ +iD.operations = {} diff --git a/js/id/operations/circular.js b/js/id/operations/circular.js new file mode 100644 index 000000000..0af64aa20 --- /dev/null +++ b/js/id/operations/circular.js @@ -0,0 +1,34 @@ +iD.operations.Circular = function(entityId, mode) { + var action = iD.actions.Circular(entityId, mode.map); + + var operation = function(history) { + var graph = history.graph(), + entity = graph.entity(entityId), + geometry = entity.geometry(graph); + + if (geometry === 'line') { + history.perform( + action, + 'made a line circular'); + + } else if (geometry === 'area') { + history.perform( + action, + 'made an area circular'); + } + }; + + operation.available = function(graph) { + var entity = graph.entity(entityId); + return entity.geometry(graph) === 'area' || entity.geometry(graph) === 'line'; + }; + + operation.enabled = function(graph) { + return action.enabled(graph); + }; + + operation.id = "circular"; + operation.title = "Circular"; + + return operation; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js new file mode 100644 index 000000000..53038dbc4 --- /dev/null +++ b/js/id/operations/delete.js @@ -0,0 +1,42 @@ +iD.operations.Delete = function(entityId) { + var operation = function(history) { + var graph = history.graph(), + entity = graph.entity(entityId), + geometry = entity.geometry(graph); + + if (geometry === 'vertex') { + history.perform( + iD.actions.DeleteNode(entityId), + 'deleted a vertex'); + + } else if (geometry === 'point') { + history.perform( + iD.actions.DeleteNode(entityId), + 'deleted a point'); + + } else if (geometry === 'line') { + history.perform( + iD.actions.DeleteWay(entityId), + 'deleted a line'); + + } else if (geometry === 'area') { + history.perform( + iD.actions.DeleteWay(entityId), + 'deleted an area'); + } + }; + + operation.available = function(graph) { + var entity = graph.entity(entityId); + return _.contains(['vertex', 'point', 'line', 'area'], entity.geometry(graph)); + }; + + operation.enabled = function() { + return true; + }; + + operation.id = "delete"; + operation.title = "Delete"; + + return operation; +}; diff --git a/js/id/operations/move.js b/js/id/operations/move.js new file mode 100644 index 000000000..9806ede67 --- /dev/null +++ b/js/id/operations/move.js @@ -0,0 +1,18 @@ +iD.operations.Move = function(entityId, mode) { + var operation = function() { + mode.controller.enter(iD.modes.MoveWay(entityId)); + }; + + operation.available = function(graph) { + return graph.entity(entityId).type === 'way'; + }; + + operation.enabled = function() { + return true; + }; + + operation.id = "move"; + operation.title = "Move"; + + return operation; +}; diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js new file mode 100644 index 000000000..941d8a4da --- /dev/null +++ b/js/id/operations/reverse.js @@ -0,0 +1,21 @@ +iD.operations.Reverse = function(entityId) { + var operation = function(history) { + history.perform( + iD.actions.ReverseWay(entityId), + 'reversed a line'); + }; + + operation.available = function(graph) { + var entity = graph.entity(entityId); + return entity.geometry(graph) === 'line'; + }; + + operation.enabled = function() { + return true; + }; + + operation.id = "reverse"; + operation.title = "Reverse"; + + return operation; +}; diff --git a/js/id/operations/split.js b/js/id/operations/split.js new file mode 100644 index 000000000..838dac88e --- /dev/null +++ b/js/id/operations/split.js @@ -0,0 +1,21 @@ +iD.operations.Split = function(entityId) { + var action = iD.actions.SplitWay(entityId); + + var operation = function(history) { + history.perform(action, 'split a way'); + }; + + operation.available = function(graph) { + var entity = graph.entity(entityId); + return entity.geometry(graph) === 'vertex'; + }; + + operation.enabled = function(graph) { + return action.enabled(graph); + }; + + operation.id = "split"; + operation.title = "Split"; + + return operation; +}; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js new file mode 100644 index 000000000..985a41697 --- /dev/null +++ b/js/id/operations/unjoin.js @@ -0,0 +1,21 @@ +iD.operations.Unjoin = function(entityId) { + var action = iD.actions.UnjoinNode(entityId); + + var operation = function(history) { + history.perform(action, 'unjoined lines'); + }; + + operation.available = function(graph) { + var entity = graph.entity(entityId); + return entity.geometry(graph) === 'vertex'; + }; + + operation.enabled = function(graph) { + return action.enabled(graph); + }; + + operation.id = "unjoin"; + operation.title = "Unjoin"; + + return operation; +}; diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index ff895ea81..d38ef3679 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -1,82 +1,16 @@ -iD.ui.RadialMenu = function(entity, history, map) { +iD.ui.RadialMenu = function(entity, mode) { var arcs; var radialMenu = function(selection, center) { - var operations, + var history = mode.map.history(), graph = history.graph(), - geometry = entity.geometry(graph); + operations = d3.values(iD.operations) + .map(function (o) { return o(entity.id, mode); }) + .filter(function (o) { return o.available(graph); }); - if (geometry === 'vertex') { - operations = [ - { - id: 'delete', - text: 'Delete', - description: 'deleted a node', - action: iD.actions.DeleteNode(entity.id) - }, - { - id: 'split', - text: 'Split Way', - description: 'split a way', - action: iD.actions.SplitWay(entity.id) - }, - { - id: 'unjoin', - text: 'Unjoin', - description: 'unjoined lines', - action: iD.actions.UnjoinNode(entity.id) - } - ]; - } else if (geometry === 'point') { - operations = [ - { - id: 'delete', - text: 'Delete', - description: 'deleted a point', - action: iD.actions.DeleteNode(entity.id) - } - ]; - } else if (geometry === 'line') { - operations = [ - { - id: 'delete', - text: 'Delete', - description: 'deleted a line', - action: iD.actions.DeleteWay(entity.id) - }, - { - id: 'reverse', - text: 'Reverse', - description: 'reversed a way', - action: iD.actions.ReverseWay(entity.id) - } - ]; - if (entity.isClosed()) { - operations.push({ - id: 'circlar', - text: 'Circular', - description: 'made way circular', - action: iD.actions.Circular(entity.id, map) - }); - } - } else if (geometry === 'area') { - operations = [ - { - id: 'delete', - text: 'Delete', - description: 'deleted an area', - action: iD.actions.DeleteWay(entity.id) - }, - { - id: 'circlar', - text: 'Circular', - description: 'made area circular', - action: iD.actions.Circular(entity.id, map) - } - ]; - } else { - // Relation, not implemented yet. - return; + function click(operation) { + d3.event.stopPropagation(); + operation(history); } var arc = d3.svg.arc() @@ -98,14 +32,14 @@ iD.ui.RadialMenu = function(entity, history, map) { arcs.append('path') .attr('class', function (d) { return 'radial-menu-item radial-menu-item-' + d.id; }) .attr('d', arc) - .classed('disabled', function (d) { return !d.action.enabled(history.graph()); }) - .on('click', function (d) { history.perform(d.action, d.description); }); + .classed('disabled', function (d) { return !d.enabled(graph); }) + .on('click', click); arcs.append('text') .attr("transform", function(d, i) { return "translate(" + arc.centroid(d, i) + ")"; }) .attr("dy", ".35em") .style("text-anchor", "middle") - .text(function(d) { return d.text; }); + .text(function(d) { return d.title; }); }; radialMenu.close = function(selection) { diff --git a/test/index.html b/test/index.html index 859d5b95d..12cf31886 100644 --- a/test/index.html +++ b/test/index.html @@ -86,7 +86,6 @@ - @@ -98,8 +97,17 @@ + + + + + + + + + diff --git a/test/spec/behavior/hover.js b/test/spec/behavior/hover.js index 817654d59..62d301d19 100644 --- a/test/spec/behavior/hover.js +++ b/test/spec/behavior/hover.js @@ -9,8 +9,23 @@ describe("iD.behavior.Hover", function() { container.remove(); }); + describe("#on", function () { + it("adds the .behavior-hover class to the selection", function () { + container.call(iD.behavior.Hover()); + expect(container).to.be.classed('behavior-hover') + }); + }); + + describe("#off", function () { + it("removes the .behavior-hover class from the selection", function () { + container.classed('behavior-hover', true); + container.call(iD.behavior.Hover().off); + expect(container).not.to.be.classed('behavior-hover') + }); + }); + describe("mouseover", function () { - it("adds the 'hover' class to all elements to which the same datum is bound", function () { + it("adds the .hover class to all elements to which the same datum is bound", function () { container.selectAll('span') .data(['a', 'b', 'a', 'b']) .enter().append('span').attr('class', Object); @@ -24,7 +39,7 @@ describe("iD.behavior.Hover", function() { }); describe("mouseout", function () { - it("removes the 'hover' class from all elements", function () { + it("removes the .hover class from all elements", function () { container.append('span').attr('class', 'hover'); container.call(iD.behavior.Hover());