text: describe('ol.control.LayerSwitcher', function() {
      var map, target, switcher;
  
      beforeEach(function() {
          target = document.createElement('div');
          document.body.appendChild(target);
          switcher = new ol.control.LayerSwitcher();
          map = new ol.Map({
              target: target,
              layers: [
                  new ol.layer.Group({
                      title: 'Base',
                      layers: [
                          new ol.layer.Tile({
                              title: 'Foo',
                              type: 'base',
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          }),
                          new ol.layer.Tile({
                              title: 'Too',
                              type: 'base',
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          })
                      ]
                  }),
                  // Combined base group
                  new ol.layer.Group({
                      title: 'Combined-Base-Group',
                      type: 'base',
                      combine: true,
                      layers: [
                          new ol.layer.Tile({
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          }),
                          new ol.layer.Tile({
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          })
                      ]
                  }),
                  // Combined overlay group
                  new ol.layer.Group({
                      title: 'Combined-Overlay-Group',
                      type: 'overlay',
                      combine: true,
                      layers: [
                          new ol.layer.Tile({
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          }),
                          new ol.layer.Tile({
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          })
                      ]
                  }),
                  // Group with no title (group and it's children should be ignored)
                  new ol.layer.Group({
                      layers: [
                          new ol.layer.Tile({
                              title: 'Never shown',
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          }),
                          new ol.layer.Tile({
                              source: new ol.source.TileDebug({
                                  projection: 'EPSG:3857',
                                  tileGrid: ol.tilegrid.createXYZ({
                                      maxZoom: 22
                                  })
                              })
                          })
                      ]
                  }),
                  new ol.layer.Tile({
                      title: 'Bar',
                      minResolution: 1000,
                      maxResolution: 5000,
                      source: new ol.source.TileDebug({
                          projection: 'EPSG:3857',
                          tileGrid: ol.tilegrid.createXYZ({
                              maxZoom: 22
                          })
                      })
                  }),
                  // Layer with no title (should be ignored)
                  new ol.layer.Tile({
                      source: new ol.source.TileDebug({
                          projection: 'EPSG:3857',
                          tileGrid: ol.tilegrid.createXYZ({
                              maxZoom: 22
                          })
                      })
                  })
              ],
              controls: [switcher]
          });
      });
  
      afterEach(function() {
          document.body.removeChild(target);
          switcher = null;
          map = null;
          target = null;
      });
  
      describe('DOM creation', function() {
          it('creates the expected DOM elements', function() {
              expect(jQuery('.layer-switcher').length).to.be(1);
          });
      });
  
      describe('Show and hide', function() {
          it('is initially hidden', function() {
              expect(jQuery('.layer-switcher').hasClass('.shown')).to.be(false);
              expect(jQuery('.layer-switcher .panel:visible').length).to.be(0);
          });
          it('is shown on button click', function() {
              jQuery('.layer-switcher button').click();
              expect(jQuery('.layer-switcher.shown').length).to.be(1);
              expect(jQuery('.layer-switcher .panel:visible').length).to.be(1);
          });
          it('is hidden on map click', function() {
              jQuery('#map').click();
              expect(jQuery('.layer-switcher').hasClass('.shown')).to.be(false);
              expect(jQuery('.layer-switcher .panel:visible').length).to.be(0);
          });
      });
  
      describe('Layer list', function() {
          it('displays all layers with a title in reverse order', function() {
              switcher.showPanel();
              var titles = jQuery('.layer-switcher label').map(function() {
                  return jQuery(this).text();
              }).get();
              expect(titles).to.eql(['Bar', 'Combined-Overlay-Group', 'Combined-Base-Group', 'Base', 'Too', 'Foo']);
          });
          it('only displays layers with a title', function() {
              switcher.showPanel();
              var elmTitles = jQuery('.layer-switcher label').map(function() {
                  return jQuery(this).text();
              }).get();
              var lyrsWithTitle = shownLyrs(map.getLayerGroup());
              expect(lyrsWithTitle.length).to.eql(elmTitles.length);
          });
          it('don\'t display layers without a title', function() {
              switcher.showPanel();
              // This is basically to ensure that our test layers include layers without a title
              var lyrsWithoutTitle = _.filter(allLyrs(map.getLayerGroup()), function(lyr) {return !lyr.get('title')});
              expect(lyrsWithoutTitle.length).not.to.equal(0);
          });
          it('displays normal layers as checkbox', function() {
              switcher.showPanel();
              var titles = jQuery('.layer-switcher input[type=checkbox]').siblings('label').map(function() {
                  return jQuery(this).text();
              }).get();
              expect(titles).to.eql(['Bar', 'Combined-Overlay-Group']);
          });
          it('greys out normal layer title labels when outside of layer resolution', function() {
              map.getView().setResolution(6000);
              switcher.showPanel();
              var layerResTooHigh = jQuery('.layer-switcher label.disabled').map(function() {
                  return jQuery(this).text();
              }).get();
              map.getView().setResolution(500);
              var layerResTooLow = jQuery('.layer-switcher label.disabled').map(function() {
                  return jQuery(this).text();
              }).get();
              expect([layerResTooHigh, layerResTooLow]).to.eql([['Bar'], ['Bar']]);
          });
          it('displays base layers as radio buttons', function() {
              switcher.showPanel();
              var titles = jQuery('.layer-switcher input[type=radio]').siblings('label').map(function() {
                  return jQuery(this).text();
              }).get();
              expect(titles).to.eql(['Combined-Base-Group', 'Too', 'Foo']);
          });
          it('should display uncombined groups without an input', function() {
              switcher.showPanel();
              var groups = jQuery('.layer-switcher label:not([for])')
              var titles = groups.map(function() {
                  return jQuery(this).text();
              }).get();
              expect(titles).to.eql(['Base']);
              expect(groups.siblings('input').length).to.be(0);
          });
          it('should display combined groups with an input', function () {
              switcher.showPanel();
              var titles = jQuery('.layer-switcher label[for]').map(function() {
                  return jQuery(this).text();
              }).get();
              expect(titles).to.contain('Combined-Base-Group');
              expect(titles).to.contain('Combined-Overlay-Group');
          });
          it('should display combined groups without sub layers', function () {
              switcher.showPanel();
              var groups = jQuery('.layer-switcher label[for]')
              expect(groups.siblings('ul').length).to.be(0);
          });
      });
  
      describe('Overlay layer visibility', function() {
          it('Toggles overlay layer visibility on click', function() {
              switcher.showPanel();
              var bar = getLayerByTitle('Bar');
              bar.setVisible(true);
              jQuery('.layer-switcher label:contains("Bar")').siblings('input').click();
              expect(bar.getVisible()).to.be(false);
              expect(jQuery('.layer-switcher label:contains("Bar")').siblings('input').get(0).checked).to.be(bar.getVisible());
              bar.setVisible(false)
              jQuery('.layer-switcher label:contains("Bar")').siblings('input').click();
              expect(bar.getVisible()).to.be(true);
              expect(jQuery('.layer-switcher label:contains("Bar")').siblings('input').get(0).checked).to.be(bar.getVisible());
          });
      });
  
      describe('Base layer visibility', function() {
          it('Only one base layer is visible after renderPanel', function() {
              var foo = getLayerByTitle('Foo');
              var too = getLayerByTitle('Too');
              var cbg = getLayerByTitle('Combined-Base-Group');
              var baseLayers = [foo, too, cbg];
              // Enable all base layers
              _.forEach(baseLayers, function (l) {
                  l.setVisible(true);
              });
  
              switcher.renderPanel();
              var visibleBaseLayerCount = _.countBy(baseLayers, function (l){
                  return l.getVisible();
              });
              expect(visibleBaseLayerCount.true).to.be(1);
          });
          it('Only top most base layer is visible after renderPanel if more than one is visible', function() {
              var foo = getLayerByTitle('Foo');
              var too = getLayerByTitle('Too');
              var cbg = getLayerByTitle('Combined-Base-Group');
              var baseLayers = [foo, too, cbg];
              // Enable all base layers
              _.forEach(baseLayers, function (l) {
                  l.setVisible(true);
              });
              switcher.renderPanel();
              expect(cbg.getVisible()).to.be(true);
          });
          it('Clicking on unchecked base layer shows it', function() {
              var too = getLayerByTitle('Too');
              too.setVisible(false);
              switcher.renderPanel();
              jQuery('.layer-switcher label:contains("Too")').siblings('input').click();
              expect(too.getVisible()).to.be(true);
              expect(jQuery('.layer-switcher label:contains("Too")').siblings('input').get(0).checked).to.be(true);
          });
          it('Clicking on checked base layer does not change base layer', function() {
              var foo = getLayerByTitle('Foo');
              foo.setVisible(true);
              switcher.renderPanel();
              jQuery('.layer-switcher label:contains("Foo")').siblings('input').click();
              expect(foo.getVisible()).to.be(true);
              expect(jQuery('.layer-switcher label:contains("Foo")').siblings('input').get(0).checked).to.be(true);
          });
      });
  
      describe('Removes cleanly', function() {
          it('Removes cleanly when ol.Map#removeControl is called', function() {
              map.removeControl(switcher);
          });
      });
  
      /**
       * Returns the title of a given layer or null if lyr is falsey
       */
      function lyrTitle(lyr) {
          return (lyr) ? lyr.get('title') : null;
      }
  
      /**
       * Returns the Layer instance that has the given title
       */
      function getLayerByTitle(title) {
          var layer = null;
          ol.control.LayerSwitcher.forEachRecursive(map, function(lyr) {
              if (lyr.get('title') && lyr.get('title') === title) {
                  layer = lyr;
                  return;
              }
          });
          return layer;
      }
  
      /**
       * Return a flattened Array of all layers regardless including those not
       * shown by the LayerSwitcher
       */
      function allLyrs(lyrs) {
          return flatten(lyrs, function (lyr) {
              return (lyr.getLayers) ? lyr.getLayers().getArray() : lyr;
          });
      }
  
      /**
       * Return a flattened Array of only those layers that the LayerSwitcher
       * should show
       */
      function shownLyrs(lyrs) {
          // Pass in the Array from the root LayerGroup as it doesn't have a
          // title but we don't want to filter out all layers
          lyrs = lyrs.getLayers().getArray();
          var flat = flatten(lyrs, function (lyr) {
              // Return a Groups layer array only if the group has a title
              // otherwise just return the group so that it's children will be
              // skipped
              return (lyr.getLayers && lyr.get('title')) ? lyr.getLayers().getArray() : lyr;
          });
          // Only return layers with a title
          return _.filter(flat, lyrTitle);
      }
  
      /**
       * Flattens a given nested collection using the provided function getArray
       * to get an Array of the collections children.
       */
      function flatten(srcCollection, getArray) {
          getArray = getArray || function (item) {return item};
          var src = getArray(srcCollection),
              dest = [];
          for (var i = 0, item; i < src.length; i++) {
              item = src[i];
              dest = dest.concat(item);
              if (_.isArray(getArray(item))) {
                  dest = dest.concat(flatten(item, getArray));
              }
          }
          return dest;
      }
  
  });
  