// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.ui.TabBarTest');
goog.setTestOnly('goog.ui.TabBarTest');

goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.testing.jsunit');
goog.require('goog.ui.Component');
goog.require('goog.ui.Container');
goog.require('goog.ui.Tab');
goog.require('goog.ui.TabBar');
goog.require('goog.ui.TabBarRenderer');

var sandbox;
var tabBar;

// Fake keyboard event object.
function FakeKeyEvent(keyCode) {
  this.keyCode = keyCode;
  this.defaultPrevented = false;
  this.propagationStopped = false;
}
FakeKeyEvent.prototype.preventDefault = function() {
  this.defaultPrevented = true;
};
FakeKeyEvent.prototype.stopPropagation = function() {
  this.propagationStopped = true;
};

function setUp() {
  sandbox = goog.dom.getElement('sandbox');
  tabBar = new goog.ui.TabBar();
}

function tearDown() {
  tabBar.dispose();
  goog.dom.removeChildren(sandbox);
}

function testConstructor() {
  assertNotNull('Tab bar must not be null', tabBar);
  assertEquals(
      'Tab bar renderer must default to expected value',
      goog.ui.TabBarRenderer.getInstance(), tabBar.getRenderer());
  assertEquals(
      'Tab bar location must default to expected value',
      goog.ui.TabBar.Location.TOP, tabBar.getLocation());
  assertEquals(
      'Tab bar orientation must default to expected value',
      goog.ui.Container.Orientation.HORIZONTAL, tabBar.getOrientation());

  var fakeRenderer = {};
  var fakeDomHelper = {};
  var bar = new goog.ui.TabBar(
      goog.ui.TabBar.Location.START, fakeRenderer, fakeDomHelper);
  assertNotNull('Tab bar must not be null', bar);
  assertEquals(
      'Tab bar renderer must have expected value', fakeRenderer,
      bar.getRenderer());
  assertEquals(
      'Tab bar DOM helper must have expected value', fakeDomHelper,
      bar.getDomHelper());
  assertEquals(
      'Tab bar location must have expected value',
      goog.ui.TabBar.Location.START, bar.getLocation());
  assertEquals(
      'Tab bar orientation must have expected value',
      goog.ui.Container.Orientation.VERTICAL, bar.getOrientation());
  bar.dispose();
}

function testDispose() {
  // Set tabBar.selectedTab_ to something non-null, just to test dispose().
  tabBar.selectedTab_ = {};
  assertNotNull('Selected tab must be non-null', tabBar.getSelectedTab());
  assertFalse('Tab bar must not have been disposed of', tabBar.isDisposed());
  tabBar.dispose();
  assertNull('Selected tab must be null', tabBar.getSelectedTab());
  assertTrue('Tab bar must have been disposed of', tabBar.isDisposed());
}

function testAddRemoveChild() {
  assertNull('No tab must be selected', tabBar.getSelectedTab());

  var first = new goog.ui.Tab('First');
  tabBar.addChild(first);
  assertEquals(
      'First tab must have been added at the expected index', 0,
      tabBar.indexOfChild(first));
  first.setSelected(true);
  assertEquals('First tab must be selected', 0, tabBar.getSelectedTabIndex());

  var second = new goog.ui.Tab('Second');
  tabBar.addChild(second);
  assertEquals(
      'Second tab must have been added at the expected index', 1,
      tabBar.indexOfChild(second));
  assertEquals(
      'First tab must remain selected', 0, tabBar.getSelectedTabIndex());

  var firstRemoved = tabBar.removeChild(first);
  assertEquals(
      'removeChild() must return the removed tab', first, firstRemoved);
  assertEquals(
      'First tab must no longer be in the tab bar', -1,
      tabBar.indexOfChild(first));
  assertEquals(
      'Second tab must be at the expected index', 0,
      tabBar.indexOfChild(second));
  assertFalse('First tab must no longer be selected', first.isSelected());
  assertTrue('Remaining tab must be selected', second.isSelected());

  var secondRemoved = tabBar.removeChild(second);
  assertEquals(
      'removeChild() must return the removed tab', second, secondRemoved);
  assertFalse('Tab must no longer be selected', second.isSelected());
  assertNull('No tab must be selected', tabBar.getSelectedTab());
}

function testGetSetLocation() {
  assertEquals(
      'Location must default to TOP', goog.ui.TabBar.Location.TOP,
      tabBar.getLocation());
  tabBar.setLocation(goog.ui.TabBar.Location.START);
  assertEquals(
      'Location must have expected value', goog.ui.TabBar.Location.START,
      tabBar.getLocation());
  tabBar.createDom();
  assertThrows(
      'Attempting to change the location after the tab bar has ' +
          'been rendered must throw error',
      function() { tabBar.setLocation(goog.ui.TabBar.Location.BOTTOM); });
}

function testIsSetAutoSelectTabs() {
  assertTrue(
      'Tab bar must auto-select tabs by default', tabBar.isAutoSelectTabs());
  tabBar.setAutoSelectTabs(false);
  assertFalse(
      'Tab bar must no longer auto-select tabs by default',
      tabBar.isAutoSelectTabs());
  tabBar.render(sandbox);
  assertFalse(
      'Rendering must not change auto-select setting',
      tabBar.isAutoSelectTabs());
  tabBar.setAutoSelectTabs(true);
  assertTrue(
      'Tab bar must once again auto-select tabs', tabBar.isAutoSelectTabs());
}

function setHighlightedIndexFromKeyEvent() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  // Verify baseline assumptions.
  assertNull('No tab must be highlighted', tabBar.getHighlighted());
  assertNull('No tab must be selected', tabBar.getSelectedTab());
  assertTrue(
      'Tab bar must auto-select tabs on keyboard highlight',
      tabBar.isAutoSelectTabs());

  // Highlight and selection must move together.
  tabBar.setHighlightedIndexFromKeyEvent(0);
  assertTrue('Foo must be highlighted', foo.isHighlighted());
  assertTrue('Foo must be selected', foo.isSelected());

  // Highlight and selection must move together.
  tabBar.setHighlightedIndexFromKeyEvent(1);
  assertFalse('Foo must no longer be highlighted', foo.isHighlighted());
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertTrue('Bar must be highlighted', bar.isHighlighted());
  assertTrue('Bar must be selected', bar.isSelected());

  // Turn off auto-select-on-keyboard-highlight.
  tabBar.setAutoSelectTabs(false);

  // Selection must not change; only highlight should move.
  tabBar.setHighlightedIndexFromKeyEvent(2);
  assertFalse('Bar must no longer be highlighted', bar.isHighlighted());
  assertTrue('Bar must remain selected', bar.isSelected());
  assertTrue('Baz must be highlighted', baz.isHighlighted());
  assertFalse('Baz must not be selected', baz.isSelected());
}

function testGetSetSelectedTab() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  assertNull('No tab must be selected', tabBar.getSelectedTab());

  tabBar.setSelectedTab(baz);
  assertTrue('Baz must be selected', baz.isSelected());
  assertEquals('Baz must be the selected tab', baz, tabBar.getSelectedTab());

  tabBar.setSelectedTab(foo);
  assertFalse('Baz must no longer be selected', baz.isSelected());
  assertTrue('Foo must be selected', foo.isSelected());
  assertEquals('Foo must be the selected tab', foo, tabBar.getSelectedTab());

  tabBar.setSelectedTab(foo);
  assertTrue('Foo must remain selected', foo.isSelected());
  assertEquals(
      'Foo must remain the selected tab', foo, tabBar.getSelectedTab());

  tabBar.setSelectedTab(null);
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertNull('No tab must be selected', tabBar.getSelectedTab());
}

function testGetSetSelectedTabIndex() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChildAt(foo = new goog.ui.Tab('foo'), 0);
  tabBar.addChildAt(bar = new goog.ui.Tab('bar'), 1);
  tabBar.addChildAt(baz = new goog.ui.Tab('baz'), 2);

  assertEquals('No tab must be selected', -1, tabBar.getSelectedTabIndex());

  tabBar.setSelectedTabIndex(2);
  assertTrue('Baz must be selected', baz.isSelected());
  assertEquals('Baz must be the selected tab', 2, tabBar.getSelectedTabIndex());

  tabBar.setSelectedTabIndex(0);
  assertFalse('Baz must no longer be selected', baz.isSelected());
  assertTrue('Foo must be selected', foo.isSelected());
  assertEquals('Foo must be the selected tab', 0, tabBar.getSelectedTabIndex());

  tabBar.setSelectedTabIndex(0);
  assertTrue('Foo must remain selected', foo.isSelected());
  assertEquals(
      'Foo must remain the selected tab', 0, tabBar.getSelectedTabIndex());

  tabBar.setSelectedTabIndex(-1);
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertEquals('No tab must be selected', -1, tabBar.getSelectedTabIndex());
}

function testDeselectIfSelected() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  // Start with the middle tab selected.
  bar.setSelected(true);
  assertTrue('Bar must be selected', bar.isSelected());
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  // Should be a no-op.
  tabBar.deselectIfSelected(null);
  assertTrue('Bar must remain selected', bar.isSelected());
  assertEquals(
      'Bar must remain the selected tab', bar, tabBar.getSelectedTab());

  // Should be a no-op.
  tabBar.deselectIfSelected(foo);
  assertTrue('Bar must remain selected', bar.isSelected());
  assertEquals(
      'Bar must remain the selected tab', bar, tabBar.getSelectedTab());

  // Should deselect bar and select the previous tab (foo).
  tabBar.deselectIfSelected(bar);
  assertFalse('Bar must no longer be selected', bar.isSelected());
  assertTrue('Foo must be selected', foo.isSelected());
  assertEquals('Foo must be the selected tab', foo, tabBar.getSelectedTab());

  // Should deselect foo and select the next tab (bar).
  tabBar.deselectIfSelected(foo);
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertTrue('Bar must be selected', bar.isSelected());
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());
}

function testHandleTabSelect() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  assertNull('No tab must be selected', tabBar.getSelectedTab());

  tabBar.handleTabSelect(
      new goog.events.Event(goog.ui.Component.EventType.SELECT, bar));
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  tabBar.handleTabSelect(
      new goog.events.Event(goog.ui.Component.EventType.SELECT, bar));
  assertEquals('Bar must remain selected tab', bar, tabBar.getSelectedTab());

  tabBar.handleTabSelect(
      new goog.events.Event(goog.ui.Component.EventType.SELECT, foo));
  assertEquals(
      'Foo must now be the selected tab', foo, tabBar.getSelectedTab());
}

function testHandleTabUnselect() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  bar.setSelected(true);
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  tabBar.handleTabUnselect(
      new goog.events.Event(goog.ui.Component.EventType.UNSELECT, foo));
  assertEquals(
      'Bar must remain the selected tab', bar, tabBar.getSelectedTab());

  tabBar.handleTabUnselect(
      new goog.events.Event(goog.ui.Component.EventType.SELECT, bar));
  assertNull('No tab must be selected', tabBar.getSelectedTab());
}

function testHandleTabDisable() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  // Start with the middle tab selected.
  bar.setSelected(true);
  assertTrue('Bar must be selected', bar.isSelected());
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  // Should deselect bar and select the previous enabled, visible tab (foo).
  bar.setEnabled(false);
  assertFalse('Bar must no longer be selected', bar.isSelected());
  assertTrue('Foo must be selected', foo.isSelected());
  assertEquals('Foo must be the selected tab', foo, tabBar.getSelectedTab());

  // Should deselect foo and select the next enabled, visible tab (baz).
  foo.setEnabled(false);
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertTrue('Baz must be selected', baz.isSelected());
  assertEquals('Baz must be the selected tab', baz, tabBar.getSelectedTab());

  // Should deselect baz.  Since there are no enabled, visible tabs left,
  // the tab bar should have no selected tab.
  baz.setEnabled(false);
  assertFalse('Baz must no longer be selected', baz.isSelected());
  assertNull('No tab must be selected', tabBar.getSelectedTab());
}

function testHandleTabHide() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'));
  tabBar.addChild(bar = new goog.ui.Tab('bar'));
  tabBar.addChild(baz = new goog.ui.Tab('baz'));

  // Start with the middle tab selected.
  bar.setSelected(true);
  assertTrue('Bar must be selected', bar.isSelected());
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  // Should deselect bar and select the previous enabled, visible tab (foo).
  bar.setVisible(false);
  assertFalse('Bar must no longer be selected', bar.isSelected());
  assertTrue('Foo must be selected', foo.isSelected());
  assertEquals('Foo must be the selected tab', foo, tabBar.getSelectedTab());

  // Should deselect foo and select the next enabled, visible tab (baz).
  foo.setVisible(false);
  assertFalse('Foo must no longer be selected', foo.isSelected());
  assertTrue('Baz must be selected', baz.isSelected());
  assertEquals('Baz must be the selected tab', baz, tabBar.getSelectedTab());

  // Should deselect baz.  Since there are no enabled, visible tabs left,
  // the tab bar should have no selected tab.
  baz.setVisible(false);
  assertFalse('Baz must no longer be selected', baz.isSelected());
  assertNull('No tab must be selected', tabBar.getSelectedTab());
}

function testHandleFocus() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'), true);
  tabBar.addChild(bar = new goog.ui.Tab('bar'), true);
  tabBar.addChild(baz = new goog.ui.Tab('baz'), true);

  // Render the tab bar into the document, so highlight handling works as
  // expected.
  tabBar.render(sandbox);

  // Start with the middle tab selected.
  bar.setSelected(true);
  assertTrue('Bar must be selected', bar.isSelected());
  assertEquals('Bar must be the selected tab', bar, tabBar.getSelectedTab());

  assertNull('No tab must be highlighted', tabBar.getHighlighted());
  tabBar.handleFocus(
      new goog.events.Event(goog.events.EventType.FOCUS, tabBar.getElement()));
  assertTrue('Bar must be highlighted', bar.isHighlighted());
  assertEquals('Bar must be the highlighted tab', bar, tabBar.getHighlighted());
}

function testHandleFocusWithoutSelectedTab() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'), true);
  tabBar.addChild(bar = new goog.ui.Tab('bar'), true);
  tabBar.addChild(baz = new goog.ui.Tab('baz'), true);

  // Render the tab bar into the document, so highlight handling works as
  // expected.
  tabBar.render(sandbox);

  // Start with no tab selected.
  assertNull('No tab must be selected', tabBar.getSelectedTab());

  assertNull('No tab must be highlighted', tabBar.getHighlighted());
  tabBar.handleFocus(
      new goog.events.Event(goog.events.EventType.FOCUS, tabBar.getElement()));
  assertTrue('Foo must be highlighted', foo.isHighlighted());
  assertEquals('Foo must be the highlighted tab', foo, tabBar.getHighlighted());
}

function testGetOrientationFromLocation() {
  assertEquals(
      goog.ui.Container.Orientation.HORIZONTAL,
      goog.ui.TabBar.getOrientationFromLocation(goog.ui.TabBar.Location.TOP));
  assertEquals(
      goog.ui.Container.Orientation.HORIZONTAL,
      goog.ui.TabBar.getOrientationFromLocation(
          goog.ui.TabBar.Location.BOTTOM));
  assertEquals(
      goog.ui.Container.Orientation.VERTICAL,
      goog.ui.TabBar.getOrientationFromLocation(goog.ui.TabBar.Location.START));
  assertEquals(
      goog.ui.Container.Orientation.VERTICAL,
      goog.ui.TabBar.getOrientationFromLocation(goog.ui.TabBar.Location.END));
}

function testKeyboardNavigation() {
  var foo, bar, baz;

  // Create a tab bar with some tabs.
  tabBar.addChild(foo = new goog.ui.Tab('foo'), true);
  tabBar.addChild(bar = new goog.ui.Tab('bar'), true);
  tabBar.addChild(baz = new goog.ui.Tab('baz'), true);
  tabBar.render(sandbox);

  // Highlight the selected tab (this happens automatically when the tab
  // bar receives keyboard focus).
  tabBar.setSelectedTabIndex(0);
  tabBar.getSelectedTab().setHighlighted(true);

  // Count events dispatched by each tab.
  var eventCount = {
    'foo': {'select': 0, 'unselect': 0},
    'bar': {'select': 0, 'unselect': 0},
    'baz': {'select': 0, 'unselect': 0}
  };

  function countEvent(e) {
    var tabId = e.target.getContent();
    var type = e.type;
    eventCount[tabId][type]++;
  }

  function getEventCount(tabId, type) { return eventCount[tabId][type]; }

  // Listen for SELECT and UNSELECT events on the tab bar.
  goog.events.listen(
      tabBar,
      [
        goog.ui.Component.EventType.SELECT, goog.ui.Component.EventType.UNSELECT
      ],
      countEvent);

  // Verify baseline assumptions.
  assertTrue('Tab bar must auto-select tabs', tabBar.isAutoSelectTabs());
  assertEquals('First tab must be selected', 0, tabBar.getSelectedTabIndex());

  // Simulate a right arrow key event.
  var rightEvent = new FakeKeyEvent(goog.events.KeyCodes.RIGHT);
  assertTrue(
      'Key event must have beeen handled', tabBar.handleKeyEvent(rightEvent));
  assertTrue(
      'Key event propagation must have been stopped',
      rightEvent.propagationStopped);
  assertTrue(
      'Default key event must have been prevented',
      rightEvent.defaultPrevented);
  assertEquals(
      'Foo must have dispatched UNSELECT', 1,
      getEventCount('foo', goog.ui.Component.EventType.UNSELECT));
  assertEquals(
      'Bar must have dispatched SELECT', 1,
      getEventCount('bar', goog.ui.Component.EventType.SELECT));
  assertEquals('Bar must have been selected', bar, tabBar.getSelectedTab());

  // Simulate a left arrow key event.
  var leftEvent = new FakeKeyEvent(goog.events.KeyCodes.LEFT);
  assertTrue(
      'Key event must have beeen handled', tabBar.handleKeyEvent(leftEvent));
  assertTrue(
      'Key event propagation must have been stopped',
      leftEvent.propagationStopped);
  assertTrue(
      'Default key event must have been prevented', leftEvent.defaultPrevented);
  assertEquals(
      'Bar must have dispatched UNSELECT', 1,
      getEventCount('bar', goog.ui.Component.EventType.UNSELECT));
  assertEquals(
      'Foo must have dispatched SELECT', 1,
      getEventCount('foo', goog.ui.Component.EventType.SELECT));
  assertEquals('Foo must have been selected', foo, tabBar.getSelectedTab());

  // Disable tab auto-selection.
  tabBar.setAutoSelectTabs(false);

  // Simulate another left arrow key event.
  var anotherLeftEvent = new FakeKeyEvent(goog.events.KeyCodes.LEFT);
  assertTrue(
      'Key event must have beeen handled',
      tabBar.handleKeyEvent(anotherLeftEvent));
  assertTrue(
      'Key event propagation must have been stopped',
      anotherLeftEvent.propagationStopped);
  assertTrue(
      'Default key event must have been prevented',
      anotherLeftEvent.defaultPrevented);
  assertEquals('Foo must remain selected', foo, tabBar.getSelectedTab());
  assertEquals(
      'Foo must not have dispatched another UNSELECT event', 1,
      getEventCount('foo', goog.ui.Component.EventType.UNSELECT));
  assertEquals(
      'Baz must not have dispatched a SELECT event', 0,
      getEventCount('baz', goog.ui.Component.EventType.SELECT));
  assertFalse('Baz must not be selected', baz.isSelected());
  assertTrue('Baz must be highlighted', baz.isHighlighted());

  // Simulate 'g' key event.
  var gEvent = new FakeKeyEvent(goog.events.KeyCodes.G);
  assertFalse(
      'Key event must not have beeen handled', tabBar.handleKeyEvent(gEvent));
  assertFalse(
      'Key event propagation must not have been stopped',
      gEvent.propagationStopped);
  assertFalse(
      'Default key event must not have been prevented',
      gEvent.defaultPrevented);
  assertEquals('Foo must remain selected', foo, tabBar.getSelectedTab());

  // Clean up.
  goog.events.unlisten(
      tabBar,
      [
        goog.ui.Component.EventType.SELECT, goog.ui.Component.EventType.UNSELECT
      ],
      countEvent);
}

function testExitAndEnterDocument() {
  var component = new goog.ui.Component();
  component.render(sandbox);

  var tab1 = new goog.ui.Tab('tab1');
  var tab2 = new goog.ui.Tab('tab2');
  var tab3 = new goog.ui.Tab('tab3');
  tabBar.addChild(tab1, true);
  tabBar.addChild(tab2, true);
  tabBar.addChild(tab3, true);

  component.addChild(tabBar, true);
  tab2.setSelected(true);
  assertEquals(tabBar.getSelectedTab(), tab2);

  component.removeChild(tabBar, true);
  tab1.setSelected(true);
  assertEquals(tabBar.getSelectedTab(), tab2);

  component.addChild(tabBar, true);
  tab3.setSelected(true);
  assertEquals(tabBar.getSelectedTab(), tab3);
}