describe('iD.actionSplit', function () { beforeEach(function () { iD.areaKeys = iD.Context().presets().areaKeys(); }); describe('#disabled', function () { it('returns falsy for a non-end node of a single way', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}) ]); expect(iD.actionSplit('b').disabled(graph)).not.to.be.ok; }); it('returns falsy for an intersection of two ways', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Node({id: '*'}), iD.Way({id: '-', nodes: ['a', '*', 'b']}), iD.Way({id: '|', nodes: ['c', '*', 'd']}) ]); expect(iD.actionSplit('*').disabled(graph)).not.to.be.ok; }); it('returns falsy for an intersection of two ways with parent way specified', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Node({id: '*'}), iD.Way({id: '-', nodes: ['a', '*', 'b']}), iD.Way({id: '|', nodes: ['c', '*', 'd']}) ]); expect(iD.actionSplit('*').limitWays(['-']).disabled(graph)).not.to.be.ok; }); it('returns falsy for a self-intersection', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a', 'd']}) ]); expect(iD.actionSplit('a').disabled(graph)).not.to.be.ok; }); it('returns \'not_eligible\' for the first node of a single way', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Way({id: '-', nodes: ['a', 'b']}) ]); expect(iD.actionSplit('a').disabled(graph)).to.equal('not_eligible'); }); it('returns \'not_eligible\' for the last node of a single way', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Way({id: '-', nodes: ['a', 'b']}) ]); expect(iD.actionSplit('b').disabled(graph)).to.equal('not_eligible'); }); it('returns \'not_eligible\' for an intersection of two ways with non-parent way specified', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Node({id: '*'}), iD.Way({id: '-', nodes: ['a', '*', 'b']}), iD.Way({id: '|', nodes: ['c', '*', 'd']}) ]); expect(iD.actionSplit('*').limitWays(['-', '=']).disabled(graph)).to.equal('not_eligible'); }); }); it('creates a new way with the appropriate nodes', function () { // Situation: // a ---- b ---- c // // Split at b. // // Expected result: // a ---- b ==== c // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); }); it('copies tags to the new way', function () { var tags = {highway: 'residential'}, graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags}) ]); graph = iD.actionSplit('b', ['='])(graph); // Immutable tags => should be shared by identity. expect(graph.entity('-').tags).to.equal(tags); expect(graph.entity('=').tags).to.equal(tags); }); it('splits a way at a T-junction', function () { // Situation: // a ---- b ---- c // | // d // // Split at b. // // Expected result: // a ---- b ==== c // | // d // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '|', nodes: ['d', 'b']}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); it('splits multiple ways at an intersection', function () { // Situation: // c // | // a ---- * ---- b // ¦ // d // // Split at b. // // Expected result: // c // | // a ---- * ==== b // ¦ // d // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Node({id: '*'}), iD.Way({id: '-', nodes: ['a', '*', 'b']}), iD.Way({id: '|', nodes: ['c', '*', 'd']}) ]); graph = iD.actionSplit('*', ['=', '¦'])(graph); expect(graph.entity('-').nodes).to.eql(['a', '*']); expect(graph.entity('=').nodes).to.eql(['*', 'b']); expect(graph.entity('|').nodes).to.eql(['c', '*']); expect(graph.entity('¦').nodes).to.eql(['*', 'd']); }); it('splits the specified ways at an intersection', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Node({id: '*'}), iD.Way({id: '-', nodes: ['a', '*', 'b']}), iD.Way({id: '|', nodes: ['c', '*', 'd']}) ]); var g1 = iD.actionSplit('*', ['=']).limitWays(['-'])(graph); expect(g1.entity('-').nodes).to.eql(['a', '*']); expect(g1.entity('=').nodes).to.eql(['*', 'b']); expect(g1.entity('|').nodes).to.eql(['c', '*', 'd']); var g2 = iD.actionSplit('*', ['¦']).limitWays(['|'])(graph); expect(g2.entity('-').nodes).to.eql(['a', '*', 'b']); expect(g2.entity('|').nodes).to.eql(['c', '*']); expect(g2.entity('¦').nodes).to.eql(['*', 'd']); var g3 = iD.actionSplit('*', ['=', '¦']).limitWays(['-', '|'])(graph); expect(g3.entity('-').nodes).to.eql(['a', '*']); expect(g3.entity('=').nodes).to.eql(['*', 'b']); expect(g3.entity('|').nodes).to.eql(['c', '*']); expect(g3.entity('¦').nodes).to.eql(['*', 'd']); }); it('splits self-intersecting ways', function () { // Situation: // b // / | // / | // c - a -- d // // Split at a. // // Expected result: // b // / | // / | // c - a == d // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a', 'd']}) ]); graph = iD.actionSplit('a', ['='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'a']); expect(graph.entity('=').nodes).to.eql(['a', 'd']); }); it('splits a closed way at the given point and its antipode', function () { // Situation: // a ---- b // | | // d ---- c // // Split at a. // // Expected result: // a ---- b // || | // d ==== c // var graph = iD.Graph([ iD.Node({id: 'a', loc: [0,1]}), iD.Node({id: 'b', loc: [1,1]}), iD.Node({id: 'c', loc: [1,0]}), iD.Node({id: 'd', loc: [0,0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) ]); var g1 = iD.actionSplit('a', ['='])(graph); expect(g1.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(g1.entity('=').nodes).to.eql(['c', 'd', 'a']); var g2 = iD.actionSplit('b', ['='])(graph); expect(g2.entity('-').nodes).to.eql(['b', 'c', 'd']); expect(g2.entity('=').nodes).to.eql(['d', 'a', 'b']); var g3 = iD.actionSplit('c', ['='])(graph); expect(g3.entity('-').nodes).to.eql(['c', 'd', 'a']); expect(g3.entity('=').nodes).to.eql(['a', 'b', 'c']); var g4 = iD.actionSplit('d', ['='])(graph); expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); }); it('splits an area by converting it to a multipolygon', function () { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0,1]}), iD.Node({id: 'b', loc: [1,1]}), iD.Node({id: 'c', loc: [1,0]}), iD.Node({id: 'd', loc: [0,0]}), iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) ]); graph = iD.actionSplit('a', ['='])(graph); expect(graph.entity('-').tags).to.eql({}); expect(graph.entity('=').tags).to.eql({}); expect(graph.parentRelations(graph.entity('-'))).to.have.length(1); var relation = graph.parentRelations(graph.entity('-'))[0]; expect(relation.tags).to.eql({type: 'multipolygon', building: 'yes'}); expect(relation.members).to.eql([ {id: '-', role: 'outer', type: 'way'}, {id: '=', role: 'outer', type: 'way'} ]); }); it('splits only the line of a node shared by a line and an area', function () { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0,1]}), iD.Node({id: 'b', loc: [1,1]}), iD.Node({id: 'c', loc: [1,0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '=', nodes: ['a', 'b', 'c', 'a'], tags: {area: 'yes'}}) ]); graph = iD.actionSplit('b', ['~'])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('~').nodes).to.eql(['b', 'c']); expect(graph.entity('=').nodes).to.eql(['a', 'b', 'c', 'a']); expect(graph.parentRelations(graph.entity('='))).to.have.length(0); }); it('adds the new way to parent relations (no connections)', function () { // Situation: // a ---- b ---- c // Relation: [----] // // Split at b. // // Expected result: // a ---- b ==== c // Relation: [----, ====] // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Relation({id: 'r', members: [{id: '-', type: 'way', role: 'forward'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '-', type: 'way', role: 'forward'}, {id: '=', type: 'way', role: 'forward'} ]); }); it('adds the new way to parent relations (forward order)', function () { // Situation: // a ---- b ---- c ~~~~ d // Relation: [----, ~~~~] // // Split at b. // // Expected result: // a ---- b ==== c ~~~~ d // Relation: [----, ====, ~~~~] // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '~', nodes: ['c', 'd']}), iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(_.map(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']); }); it('adds the new way to parent relations (reverse order)', function () { // Situation: // a ---- b ---- c ~~~~ d // Relation: [~~~~, ----] // // Split at b. // // Expected result: // a ---- b ==== c ~~~~ d // Relation: [~~~~, ====, ----] // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '~', nodes: ['c', 'd']}), iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(_.map(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']); }); it('handles incomplete relations', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(_.map(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']); }); it('converts simple multipolygon to a proper multipolygon', function () { var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Way({'id': '-', nodes: ['a', 'b', 'c'], tags: {natural: 'water'}}), iD.Relation({id: 'r', members: [{id: '-', type: 'way', role: 'outer'}], tags: {type: 'multipolygon'}}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('-').tags).to.eql({}); expect(graph.entity('r').tags).to.eql({type: 'multipolygon', natural: 'water'}); expect(_.map(graph.entity('r').members, 'id')).to.eql(['-', '=']); }); ['restriction', 'restriction:bus'].forEach(function (type) { it('updates a restriction\'s \'from\' role', function () { // Situation: // a ----> b ----> c ~~~~ d // A restriction from ---- to ~~~~ via c. // // Split at b. // // Expected result: // a ----> b ====> c ~~~~ d // A restriction from ==== to ~~~~ via c. // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '~', nodes: ['c', 'd']}), iD.Relation({id: 'r', tags: {type: type}, members: [ {id: '-', role: 'from'}, {id: '~', role: 'to'}, {id: 'c', role: 'via'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '=', role: 'from'}, {id: '~', role: 'to'}, {id: 'c', role: 'via'}]); }); it('updates a restriction\'s \'to\' role', function () { // Situation: // a ----> b ----> c ~~~~ d // A restriction from ~~~~ to ---- via c. // // Split at b. // // Expected result: // a ----> b ====> c ~~~~ d // A restriction from ~~~~ to ==== via c. // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '~', nodes: ['c', 'd']}), iD.Relation({id: 'r', tags: {type: type}, members: [ {id: '~', role: 'from'}, {id: '-', role: 'to'}, {id: 'c', role: 'via'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '~', role: 'from'}, {id: '=', role: 'to'}, {id: 'c', role: 'via'}]); }); it('leaves unaffected restrictions unchanged', function () { // Situation: // a <---- b <---- c ~~~~ d // A restriction from ---- to ~~~~ via c. // // Split at b. // // Expected result: // a <==== b <---- c ~~~~ d // A restriction from ---- to ~~~~ via c. // var graph = iD.Graph([ iD.Node({id: 'a'}), iD.Node({id: 'b'}), iD.Node({id: 'c'}), iD.Node({id: 'd'}), iD.Way({id: '-', nodes: ['c', 'b', 'a']}), iD.Way({id: '~', nodes: ['c', 'd']}), iD.Relation({id: 'r', tags: {type: type}, members: [ {id: '-', role: 'from'}, {id: '~', role: 'to'}, {id: 'c', role: 'via'}]}) ]); graph = iD.actionSplit('b', ['='])(graph); expect(graph.entity('r').members).to.eql([ {id: '-', role: 'from'}, {id: '~', role: 'to'}, {id: 'c', role: 'via'}]); }); }); });