describe('iD.Lanes', function() {

    describe('default lane tags', function() {

        describe('motorway', function() {

            it('returns 2 lanes for highway=motorway', function() {
                expect(iD.osmWay({tags: { highway: 'motorway' }}).lanes().metadata.count, 'motorway lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'motorway', oneway: 'yes' }}).lanes().metadata.count, 'motorway lanes')
                    .to.eql(2);
            });

            it('returns 4 lanes for highway=motorway and oneway=no', function() {
                expect(iD.osmWay({tags: { highway: 'motorway', oneway: 'no' }}).lanes().metadata.count, 'motorway lanes')
                    .to.eql(4);
            });

            it('returns 2 lanes for highway=motorway_link', function() {
                expect(iD.osmWay({tags: { highway: 'motorway_link' }}).lanes().metadata.count, 'motorway_link lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'motorway_link', oneway: 'no' }}).lanes().metadata.count, 'motorway_link lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=motorway_link and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'motorway_link', oneway: 'yes' }}).lanes().metadata.count, 'motorway_link lanes')
                    .to.eql(1);
            });
        });

        describe('trunk', function() {

            it('returns 4 lanes for highway=trunk', function() {
                expect(iD.osmWay({tags: { highway: 'trunk' }}).lanes().metadata.count, 'trunk lanes')
                    .to.eql(4);
                expect(iD.osmWay({tags: { highway: 'trunk', oneway: 'no' }}).lanes().metadata.count, 'trunk lanes')
                    .to.eql(4);
            });

            it('returns 2 lanes for highway=trunk and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'trunk', oneway: 'yes' }}).lanes().metadata.count, 'trunk lanes')
                    .to.eql(2);
            });

            it('returns 2 lanes for highway=trunk_link', function() {
                expect(iD.osmWay({tags: { highway: 'trunk_link' }}).lanes().metadata.count, 'trunk_link lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'trunk_link', oneway: 'no' }}).lanes().metadata.count, 'trunk_link lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=trunk_link and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'trunk_link', oneway: 'yes' }}).lanes().metadata.count, 'trunk_link lanes')
                    .to.eql(1);
            });
        });

        describe('primary', function() {

            it('returns 2 lanes for highway=primary', function() {
                expect(iD.osmWay({tags: { highway: 'primary' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'primary', oneway: 'no' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=primary and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'primary', oneway: 'yes' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(1);
            });

            it('returns 2 lanes for highway=primary_link', function() {
                expect(iD.osmWay({tags: { highway: 'primary_link' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'primary_link', oneway: 'no' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=primary_link and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'primary_link', oneway: 'yes' }}).lanes().metadata.count, 'primary lanes')
                    .to.eql(1);
            });
        });

        describe('seconday', function() {

            it('returns 2 lanes for highway=secondary', function() {
                expect(iD.osmWay({tags: { highway: 'secondary' }}).lanes().metadata.count, 'secondary lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'secondary', oneway: 'no' }}).lanes().metadata.count, 'secondary lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=secondary and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'secondary', oneway: 'yes' }}).lanes().metadata.count, 'secondary lanes')
                    .to.eql(1);
            });

            it('returns 2 lane for highway=secondary_link', function() {
                expect(iD.osmWay({tags: { highway: 'secondary_link' }}).lanes().metadata.count, 'secondary_link lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'secondary_link', oneway: 'no' }}).lanes().metadata.count, 'secondary_link lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=secondary_link and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'secondary_link', oneway: 'yes' }}).lanes().metadata.count, 'secondary_link lanes')
                    .to.eql(1);
            });
        });

        describe('tertiary', function() {

            it('returns 2 lanes for highway=tertiary', function() {
                expect(iD.osmWay({tags: { highway: 'tertiary' }}).lanes().metadata.count, 'tertiary lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'tertiary', oneway: 'no' }}).lanes().metadata.count, 'tertiary lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=tertiary and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'tertiary', oneway: 'yes' }}).lanes().metadata.count, 'tertiary lanes')
                    .to.eql(1);
            });

            it('returns 2 lane for highway=tertiary_link', function() {
                expect(iD.osmWay({tags: { highway: 'tertiary_link' }}).lanes().metadata.count, 'tertiary_link lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'tertiary_link', oneway: 'no' }}).lanes().metadata.count, 'tertiary_link lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=tertiary_link and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'tertiary_link', oneway: 'yes' }}).lanes().metadata.count, 'tertiary_link lanes')
                    .to.eql(1);
            });
        });

        describe('residential', function() {

            it('returns 2 lanes for highway=residential', function() {
                expect(iD.osmWay({tags: { highway: 'residential' }}).lanes().metadata.count, 'residential lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'residential', oneway: 'no' }}).lanes().metadata.count, 'residential lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=residential and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'residential', oneway: 'yes' }}).lanes().metadata.count, 'residential lanes')
                    .to.eql(1);
            });
        });

        describe('service', function() {

            it('returns 2 lanes for highway=service', function() {
                expect(iD.osmWay({tags: { highway: 'service' }}).lanes().metadata.count, 'service lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'service', oneway: 'no' }}).lanes().metadata.count, 'service lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=service and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'service', oneway: 'yes' }}).lanes().metadata.count, 'service lanes')
                    .to.eql(1);
            });
        });

        describe('track', function() {

            it('returns 2 lanes for highway=track', function() {
                expect(iD.osmWay({tags: { highway: 'track' }}).lanes().metadata.count, 'track lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'track', oneway: 'no' }}).lanes().metadata.count, 'track lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=track and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'track', oneway: 'yes' }}).lanes().metadata.count, 'track lanes')
                    .to.eql(1);
            });
        });

        describe('path', function() {

            it('returns 2 lanes for highway=path', function() {
                expect(iD.osmWay({tags: { highway: 'path' }}).lanes().metadata.count, 'path lanes')
                    .to.eql(2);
                expect(iD.osmWay({tags: { highway: 'path', oneway: 'no' }}).lanes().metadata.count, 'path lanes')
                    .to.eql(2);
            });

            it('returns 1 lane for highway=path and oneway=yes', function() {
                expect(iD.osmWay({tags: { highway: 'path', oneway: 'yes' }}).lanes().metadata.count, 'path lanes')
                    .to.eql(1);
            });
        });
    });

    describe('oneway tags', function() {
        it('returns correctly oneway when tagged as oneway', function() {
            expect(iD.osmWay({tags: { highway: 'residential', oneway: 'yes' }}).lanes().metadata.oneway, 'residential lanes')
                .to.be.true;
            expect(iD.osmWay({tags: { highway: 'residential', oneway: 'no' }}).lanes().metadata.oneway, 'residential lanes')
                .to.be.false;
        });
    });

    describe('lane direction', function() {

        it('returns correctly the lane:forward and lane:backward count', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 2, 'lanes:forward': 1, 'lanes:backward': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 2,
                    oneway: false,
                    forward: 1,
                    backward: 1,
                    bothways: 0
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 4, 'lanes:forward': 3, 'lanes:backward': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 4,
                    oneway: false,
                    forward: 3,
                    backward: 1,
                    bothways: 0
                });
        });

        it('returns correctly the count if erroneous values are supplied', function() {
            expect(iD.osmWay({tags: { highway: 'trunk', lanes: 2, 'lanes:forward': 3 }}).lanes().metadata, 'trunk lanes')
                .to.include({
                    count: 2,
                    oneway: false,
                    forward: 2,
                    backward: 0,
                    bothways: 0
                });
        });

        it('returns correctly forward count when oneway=yes', function() {
            expect(iD.osmWay({tags: { highway: 'trunk', lanes: 2, oneway: 'yes' }}).lanes().metadata, 'trunk lanes')
                .to.include({
                    count: 2,
                    oneway: true,
                    forward: 2,
                    backward: 0,
                    bothways: 0
                });
        });

        it('returns correctly backward count the when oneway=-1', function() {
            expect(iD.osmWay({tags: { highway: 'primary', lanes: 4, oneway: '-1' }}).lanes().metadata, 'primary lanes')
                .to.include({
                    count: 4,
                    oneway: true,
                    backward: 4,
                    forward: 0,
                    bothways: 0
                });
        });

        it('skips provided lanes:forward value when oneway=yes', function() {
            expect(iD.osmWay({tags: { highway: 'trunk', lanes: 2, oneway: 'yes', 'lanes:forward': 1 }}).lanes().metadata, 'trunk lanes')
                .to.include({
                    count: 2,
                    oneway: true,
                    forward: 2,
                    backward: 0,
                    bothways: 0
                });
        });

        it('skips provided lanes:backward value when oneway=yes', function() {
            expect(iD.osmWay({tags: { highway: 'trunk', lanes: 2, oneway: 'yes', 'lanes:backward': 1 }}).lanes().metadata, 'trunk lanes')
                .to.include({
                    count: 2,
                    oneway: true,
                    forward: 2,
                    backward: 0,
                    bothways: 0
                });
        });

        it('returns correctly forward count if only backward is supplied', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 3, 'lanes:backward': 1, }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 3,
                    oneway: false,
                    forward: 2,
                    backward: 1,
                    bothways: 0
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 4, 'lanes:backward': 3, }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 4,
                    oneway: false,
                    forward: 1,
                    backward: 3,
                    bothways: 0
                });
        });

        it('returns correctly backward count if only forward is supplied', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 3, 'lanes:forward': 1, }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 3,
                    oneway: false,
                    forward: 1,
                    backward: 2,
                    bothways: 0
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 2, 'lanes:forward': 1, }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 2,
                    oneway: false,
                    forward: 1,
                    backward: 1,
                    bothways: 0
                });
        });

        it('returns correctly backward count if forward and both_ways are supplied', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 3, 'lanes:forward': 1, 'lanes:both_ways': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 3,
                    oneway: false,
                    forward: 1,
                    backward: 1,
                    bothways: 1
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:forward': 1, 'lanes:both_ways': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 5,
                    oneway: false,
                    forward: 1,
                    backward: 3,
                    bothways: 1
                });
        });

        it('returns correctly forward count if backward and both_ways are supplied', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 3, 'lanes:backward': 1, 'lanes:both_ways': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 3,
                    oneway: false,
                    forward: 1,
                    backward: 1,
                    bothways: 1
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:backward': 1, 'lanes:both_ways': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 5,
                    oneway: false,
                    forward: 3,
                    backward: 1,
                    bothways: 1
                });
        });

        it('returns correctly the lane:both_ways count as 1', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 2, 'lanes:forward': 1, 'lanes:both_ways': 1 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 2,
                    oneway: false,
                    forward: 1,
                    backward: 0,
                    bothways: 1
                });
        });

        it('returns correctly when lane:both_ways>1', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:forward': 2, 'lanes:both_ways': 2, 'lanes:backward': 2 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 5,
                    oneway: false,
                    forward: 2,
                    backward: 2,
                    bothways: 1
                });
        });

        it('returns correctly when lane:both_ways is 0 or Not a Number', function() {
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:forward': 2, 'lanes:both_ways': 0, 'lanes:backward': 3 }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 5,
                    oneway: false,
                    forward: 2,
                    backward: 3,
                    bothways: 0
                });
            expect(iD.osmWay({tags: { highway: 'residential', lanes: 2, 'lanes:forward': 1, 'lanes:both_ways': 'none' }}).lanes().metadata, 'residential lanes')
                .to.include({
                    count: 2,
                    oneway: false,
                    forward: 1,
                    backward: 1,
                    bothways: 0
                });
        });

    });

    describe.skip('lanes array', function() {
      it('should have correct number of direction elements', function() {
        var lanes = iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:forward': 2, 'lanes:both_ways': 0, 'lanes:backward': 3 }}).lanes().lanes;
        var forward = lanes.filter(function(l) {
          return l.direction === 'forward';
        });
        var backward = lanes.filter(function(l) {
          return l.direction === 'backward';
        });
        var bothways = lanes.filter(function(l) {
          return l.direction === 'bothways';
        });
        expect(forward.length).to.eql(2);
        expect(backward.length).to.eql(3);
        expect(bothways.length).to.eql(0);

      });
      it('should have correct number of direction elements', function() {
        var lanes = iD.osmWay({tags: { highway: 'residential', lanes: 5, 'lanes:backward': 1, 'lanes:both_ways': 1 }}).lanes().lanes;
        var forward = lanes.filter(function(l) {
          return l.direction === 'forward';
        });
        var backward = lanes.filter(function(l) {
          return l.direction === 'backward';
        });
        var bothways = lanes.filter(function(l) {
          return l.direction === 'bothways';
        });
        expect(forward.length).to.eql(3);
        expect(backward.length).to.eql(1);
        expect(bothways.length).to.eql(1);
      });
    });

    describe('turn lanes', function() {
        it('returns correctly when oneway=yes', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'trunk',
                    oneway: 'yes',
                    'turn:lanes': 'none|slight_right'
                }
            }).lanes().metadata;
            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([
                    ['none'], ['slight_right']
                ]);
        });

        it('returns correctly when oneway=yes and lanes=2', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    oneway: 'yes',
                    lanes: '2',
                    'turn:lanes': 'none|slight_right'
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([
                    ['none'], ['slight_right']
                ]);
        });

        it('returns correctly when lanes=5 and both_ways=1', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'lanes:forward': 1,
                    'lanes:both_ways': 1,
                    'turn:lanes:forward': 'slight_left',
                    'turn:lanes:backward': 'none|through|through;slight_right',
                }
            }).lanes().metadata;
            expect(metadata.turnLanes.forward)
                .to.deep.equal([
                    ['slight_left']
                ]);
            expect(metadata.turnLanes.backward)
                .to.deep.equal([
                    ['none'], ['through'], ['through', 'slight_right']
                ]);
        });

        it('returns correctly when multiple values are present in a lane and oneway=yes', function() {
            var lanesData = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'yes',
                    'turn:lanes': 'slight_left;reverse;left|slight_left;left;through|through|none|through;right',
                }
            }).lanes();

            expect(lanesData.metadata.turnLanes.unspecified)
                .to.deep.equal([
                    ['slight_left', 'reverse', 'left'],
                    ['slight_left', 'left', 'through'],
                    ['through'],
                    ['none'],
                    ['through', 'right']
                ]);
        });

        it('returns correctly when multiple values are present in a lane and oneway=no', function() {
            var lanesData = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'no',
                    'lanes:forward': 3,
                    'lanes:backward': 2,
                    'turn:lanes:forward': 'slight_left;reverse;left|slight_left;left;through|through',
                    'turn:lanes:backward': 'none|through;left'
                }
            }).lanes();
            expect(lanesData.metadata.turnLanes.forward)
                .to.deep.equal([
                    ['slight_left', 'reverse', 'left'],
                    ['slight_left', 'left', 'through'],
                    ['through']
                ]);
            expect(lanesData.metadata.turnLanes.backward)
                .to.deep.equal([
                    ['none'],
                    ['through', 'left']
                ]);
        });

        it('returns unknown for every invalid value in turn:lanes', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 3,
                    oneway: 'yes',
                    'turn:lanes': '||straight;NO_LEFT',
                }
            }).lanes().metadata;
            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([
                    ['none'], ['none'], ['unknown', 'unknown']
                ]);
        });

        it('returns unknown for every invalid value in turn:lanes:forward & turn:lanes:backward', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'lanes:forward': 1,
                    'lanes:both_ways': 1,
                    'turn:lanes:forward': 'sligh_left',
                    'turn:lanes:backward': 'none|through|though;slight_right',
                }
            }).lanes().metadata;
            expect(metadata.turnLanes.forward)
                .to.deep.equal([
                    ['unknown']
                ]);
            expect(metadata.turnLanes.backward)
                .to.deep.equal([
                    ['none'], ['through'], ['unknown', 'slight_right']
                ]);
        });

        it.skip('fills with [\'unknown\'] when given turn:lanes are less than lanes count', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'yes',
                    'turn:lanes': 'slight_left|',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([
                    ['slight_left'], ['none']
                ]);
        });

        it.skip('fills with [\'unknown\'] when given turn:lanes:forward are less than lanes forward count', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'no',
                    'lanes:forward': 3,
                    'turn:lanes:forward': 'slight_left',
                    'turn:lanes:backward': 'through',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.forward)
                .to.deep.equal([
                    ['slight_left'], ['unknown'], ['unknown']
                ]);
            expect(metadata.turnLanes.backward)
                .to.deep.equal([
                    ['through'], ['unknown']
                ]);
        });

        it.skip('clips when turn lane information is more than lane count', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 2,
                    oneway: 'yes',
                    'turn:lanes': 'through|through;slight_right|slight_right',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes)
                .to.deep.equal([
                    ['through'], ['through', 'slight_right']
                ]);
        });

        it('turnLanes is undefined when not present', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 2,
                    oneway: 'yes'
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.equal(undefined);
            expect(metadata.turnLanes.forward)
                .to.equal(undefined);
            expect(metadata.turnLanes.backward)
                .to.equal(undefined);
        });

        it('turnLanes.forward and turnLanes.backward are both undefined when both are not provided', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 2,
                    oneway: 'yes',
                    'turn:lanes': 'through|through;slight_right',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([['through'], ['through', 'slight_right']]);
            expect(metadata.turnLanes.forward)
                .to.equal(undefined);
            expect(metadata.turnLanes.backward)
                .to.equal(undefined);
        });

        it('parses turnLane correctly when lanes:both_ways=1', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'no',
                    'lanes:forward': 3,
                    'lanes:both_ways': 1,
                    'lanes:backward': 1,
                    'turn:lanes:backward': 'slight_right',
                    'turn:lanes:forward': 'slight_left||',
                }
            }).lanes();
            expect(lanes.metadata.turnLanes.backward)
                .to.deep.equal([['slight_right']]);
            expect(lanes.metadata.turnLanes.forward)
                .to.deep.equal([['slight_left'], ['none'], ['none']]);
        });

        it('parses turnLane correctly when lanes:both_ways=1 & lanes:forward < lanes:backward', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'no',
                    'lanes:forward': 1,
                    'lanes:both_ways': 1,
                    'lanes:backward': 3,
                    'turn:lanes:forward': 'through',
                    'turn:lanes:backward': 'slight_left||',
                }
            }).lanes();
            expect(lanes.metadata.turnLanes.forward)
                .to.deep.equal([['through']]);
            expect(lanes.metadata.turnLanes.backward)
                .to.deep.equal([['slight_left'], ['none'], ['none']]);
        });

        it('parses correctly when turn:lanes= ||x', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 3,
                    oneway: 'yes',
                    'turn:lanes': '||through;slight_right',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([['none'], ['none'], ['through', 'slight_right']]);
        });

        it('parses correctly when turn:lanes= |x|', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    'turn:lanes': '|through|',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.unspecified)
                .to.deep.equal([['none'], ['through'], ['none']]);
        });

        it('parses correctly when turn:lanes:forward= ||x', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 4,
                    oneway: 'no',
                    'lanes:forward': 3,
                    'lanes:backward': 1,
                    'turn:lanes:forward': '||through;slight_right',
                    'turn:lanes:backward': 'none',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.forward)
                .to.deep.equal([['none'], ['none'], ['through', 'slight_right']]);
            expect(metadata.turnLanes.backward)
                .to.deep.equal([['none']]);
        });

        it('parses correctly when turn:lanes:backward= |', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'no',
                    'lanes:forward': 3,
                    'lanes:backward': 2,
                    'turn:lanes:backward': '|',
                    'turn:lanes:forward': 'slight_left||',
                }
            }).lanes().metadata;

            expect(metadata.turnLanes.forward)
                .to.deep.equal([['slight_left'], ['none'], ['none']]);
            expect(metadata.turnLanes.backward)
                .to.deep.equal([['none'], ['none']]);
        });

        it('fills lanes.unspecified with key \'turnLane\' correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    oneway: 'yes',
                    'turn:lanes': 'slight_left||through|through;slight_right|slight_right'
                }
            }).lanes().lanes;
            var turnLanesUnspecified = lanes.unspecified.map(function(l) { return l.turnLane; });
            expect(turnLanesUnspecified).to.deep.equal([
                ['slight_left'], ['none'], ['through'], ['through', 'slight_right'], ['slight_right']
            ]);
            expect(lanes.forward).to.deep.equal([]);
            expect(lanes.backward).to.deep.equal([]);
        });

        it('fills lanes.forward & lanes.backward with key \'turnLane\' correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'tertiary',
                    lanes: 5,
                    'lanes:forward': 3,
                    'lanes:backward': 2,
                    'turn:lanes:backward': 'none|slight_right',
                    'turn:lanes:forward': 'slight_left||',
                }
            }).lanes().lanes;
            expect(lanes.unspecified).to.deep.equal([]);
            var turnLanesForward = lanes.forward.map(function(l) { return l.turnLane; });
            var turnLanesBackward = lanes.backward.map(function(l) { return l.turnLane; });
            expect(turnLanesForward).to.deep.equal([
                ['slight_left'], ['none'], ['none']
            ]);
            expect(turnLanesBackward).to.deep.equal([
                ['none'], ['slight_right']
            ]);
        });
    });

    describe('maxspeed', function() {
        it('should parse maxspeed without any units correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
            maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': 70
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
        });

        it('should parse maxspeed with km/h correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70 km/h'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
        });

        it('should parse maxspeed with kmh correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70kmh'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
        });

        it('should parse maxspeed with kph correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70 kph'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
        });

        it('should parse maxspeed with mph correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70mph'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(70);
        });

        it('should parse maxspeed with knots correctly', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '50knots'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(50);
        });

        it('should return undefined when incorrect maxspeed unit provided ', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': '70km'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(undefined);
        });

        it('should return undefined when incorrect maxspeed value provided ', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed': 'a70knots'
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(undefined);
        });

        it('should return undefined when maxspeed not provided ', function() {
            var maxspeed = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                }
            }).lanes().metadata.maxspeed;
            expect(maxspeed).to.equal(undefined);
        });
    });

    describe('maxspeed:lanes', function() {

        it('should parse correctly', function() {
            var maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed:lanes': '30|40|40|40|40'
                }
            }).lanes().metadata.maxspeedLanes.unspecified;
            expect(maxspeedLanes).to.deep.equal([
                30, 40, 40, 40, 40
            ]);
        });

        it('should parse maxspeed:lanes:forward/backward correctly', function() {
            var metadata = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: 30,
                    'lanes:forward': 4,
                    'lanes:backward': 1,
                    'maxspeed:lanes:forward': '30|40|40|40',
                    'maxspeed:lanes:backward': '30'
                }
            }).lanes().metadata;
            expect(metadata.maxspeedLanes.forward).to.deep.equal([
                null, 40, 40, 40
            ]);
            expect(metadata.maxspeedLanes.backward).to.deep.equal([
                null
            ]);
        });

        it('should parse correctly when some values maxspeed:lanes are implied by x||y notation', function() {
            var maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 4,
                    maxspeed: '40kmh',
                    'maxspeed:lanes': '30|||40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                30, null, null, null
            ]);
        });

        it('should parse correctly when some values maxspeed:lanes are implied by x||| notation', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'lanes:forward': 1,
                    'lanes:both_ways': 1,
                    'turn:lanes:forward': 'slight_left',
                    'turn:lanes:backward': 'none|through|through;slight_right',
                    maxspeed: '60kmh',
                    'maxspeed:lanes': '30|||'
                }
            }).lanes();
            expect(lanes.metadata.maxspeedLanes.unspecified).to.deep.equal([
                30, null, null, null
            ]);
        });

        it('should return none for each maxspeed:lanes which equals maxspeed', function() {
            var maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '40kmh',
                    'maxspeed:lanes': '30|40|40|40|40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                30, null, null, null, null
            ]);
            maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '50kmh',
                    'maxspeed:lanes': '30|40|40|40|40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                30, 40, 40, 40, 40
            ]);
            maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30knots',
                    'maxspeed:lanes': '30|40|40|40|40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                null, 40, 40, 40, 40
            ]);
        });

        it('should return \'unknown\' for every invalid maxspeed:lane value', function() {
            var maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30kmh',
                    'maxspeed:lanes': '30|40|forty|40|40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                null, 40, 'unknown', 40, 40
            ]);
            maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30kmh',
                    'maxspeed:lanes': '30|40|forty|40|random'
                }
            }).lanes().metadata.maxspeedLanes;

            expect(maxspeedLanes.unspecified).to.deep.equal([
                null, 40, 'unknown', 40, 'unknown'
            ]);
        });

        it('should parse maxspeed when none', function() {
            var maxspeedLanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    'maxspeed:lanes': '30|40|none|40|40'
                }
            }).lanes().metadata.maxspeedLanes;
            expect(maxspeedLanes.unspecified).to.deep.equal([
                30, 40, 'none', 40, 40
            ]);
        });

        it('fills lanes.unspecified with key \'maxspeed\' correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30kmh',
                    'maxspeed:lanes': '30|40|forty|40|40'
                }
            }).lanes().lanes;
            var maxspeedLanes = lanes.unspecified.map(function (l) {
                return l.maxspeed;
            });
            expect(maxspeedLanes).to.deep.equal([
                null, 40, 'unknown', 40, 40
            ]);
        });
    });

    describe('bicycle lanes', function() {
        it('should parse bicycle:lanes correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 3,
                    'lanes:bicycleway': 2,
                    'bicycleway:lanes': 'no|yes|no|designated|no',
                    maxspeed: '30kmh',
                    'turn:lanes': 'left|||through|right'
                }
            }).lanes();
            expect(lanes.metadata.bicyclewayLanes.unspecified).to.deep.equal([
                'no','yes','no', 'designated', 'no'
            ]);
            var bicyclewayLanes = lanes.lanes.unspecified.map(function(l) {
                return l.bicycleway;
            });
            expect(bicyclewayLanes).to.deep.equal([
                'no','yes','no', 'designated', 'no'
            ]);
        });

        it('should parse bicycle:lanes:forward/backward correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    'lanes:forward': 4,
                    'lanes:backward': 3,
                    'turn:lanes:forward': 'left;through|left;through|through|right;through|right',
                    'bicycleway:lanes:forward': 'lane|no|no|no|no',
                    'bicycleway:lanes:backward': 'lane|no|no|no'
                }
            }).lanes();
            expect(lanes.metadata.bicyclewayLanes.forward).to.deep.equal([
                'lane','no','no', 'no', 'no'
            ]);
            expect(lanes.metadata.bicyclewayLanes.backward).to.deep.equal([
                'lane','no','no', 'no'
            ]);
            var bicyclewayLanesForward = lanes.lanes.forward.map(function(l) {
                return l.bicycleway;
            });
            expect(bicyclewayLanesForward).to.deep.equal([
                'lane','no','no', 'no', 'no'
            ]);
            var bicyclewayLanesBackward = lanes.lanes.backward.map(function(l) {
                return l.bicycleway;
            });
            expect(bicyclewayLanesBackward).to.deep.equal([
                'lane','no','no', 'no'
            ]);
        });

        it('should replace any invalid value with unknown', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 3,
                    maxspeed: '30kmh',
                    'bicycleway:lanes': 'no|share|no|designated|no',
                    'turn:lanes': 'left|||through|right'
                }
            }).lanes();
            expect(lanes.metadata.bicyclewayLanes.unspecified).to.deep.equal([
                'no','unknown','no', 'designated', 'no'
            ]);
            var psvLanesForward = lanes.lanes.unspecified.map(function(l) {
                return l.bicycleway;
            });
            expect(psvLanesForward).to.deep.equal([
                'no','unknown','no', 'designated', 'no'
            ]);
        });
    });

    describe('miscellaneous lanes', function() {
        it('should parse psv:lanes correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30kmh',
                    'psv:lanes': 'yes|no||no|no'
                }
            }).lanes();
            expect(lanes.metadata.psvLanes.unspecified).to.deep.equal([
                'yes','no','no', 'no', 'no'
            ]);
            var psvLanesForward = lanes.lanes.unspecified.map(function(l) {
                return l.psv;
            });
            expect(psvLanesForward).to.deep.equal([
                'yes','no','no', 'no', 'no'
            ]);
        });
        it('should parse psv:lanes:forward/backward correctly', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 5,
                    maxspeed: '30kmh',
                    'psv:lanes:forward': 'no|no|',
                    'psv:lanes:backward': 'yes|designated',
                }
            }).lanes();
            expect(lanes.metadata.psvLanes.forward).to.deep.equal([
                'no','no','no'
            ]);
            expect(lanes.metadata.psvLanes.backward).to.deep.equal([
                'yes', 'designated'
            ]);
            var psvLanesForward = lanes.lanes.forward.map(function(l) {
                return l.psv;
            });
            var psvLanesBackward = lanes.lanes.backward.map(function(l) {
                return l.psv;
            });
            expect(psvLanesForward).to.deep.equal([
                'no','no','no'
            ]);
            expect(psvLanesBackward).to.deep.equal([
                'yes', 'designated'
            ]);
        });
        it('should replace any invalid value with unknown', function() {
            var lanes = iD.osmWay({
                tags: {
                    highway: 'residential',
                    lanes: 3,
                    maxspeed: '30kmh',
                    'psv:lanes': 'yes|no|garbage'
                }
            }).lanes();
            expect(lanes.metadata.psvLanes.unspecified).to.deep.equal([
                'yes','no', 'unknown'
            ]);
            var psvLanesForward = lanes.lanes.unspecified.map(function(l) {
                return l.psv;
            });
            expect(psvLanesForward).to.deep.equal([
                'yes','no', 'unknown'
            ]);
        });
    });
});
