// Copyright 2009 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.CheckboxTest');
goog.setTestOnly('goog.ui.CheckboxTest');

goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.Role');
goog.require('goog.a11y.aria.State');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.KeyCodes');
goog.require('goog.testing.events');
goog.require('goog.testing.jsunit');
goog.require('goog.ui.Checkbox');
goog.require('goog.ui.CheckboxRenderer');
goog.require('goog.ui.Component');
goog.require('goog.ui.ControlRenderer');
goog.require('goog.ui.decorate');

var checkbox;

function setUp() {
  checkbox = new goog.ui.Checkbox();
}

function tearDown() {
  checkbox.dispose();
}

function testClassNames() {
  checkbox.createDom();

  checkbox.setChecked(false);
  assertSameElements('classnames of unchecked checkbox',
      ['goog-checkbox', 'goog-checkbox-unchecked'],
      goog.dom.classlist.get(checkbox.getElement()));

  checkbox.setChecked(true);
  assertSameElements('classnames of checked checkbox',
      ['goog-checkbox', 'goog-checkbox-checked'],
      goog.dom.classlist.get(checkbox.getElement()));

  checkbox.setChecked(null);
  assertSameElements('classnames of partially checked checkbox',
      ['goog-checkbox', 'goog-checkbox-undetermined'],
      goog.dom.classlist.get(checkbox.getElement()));

  checkbox.setEnabled(false);
  assertSameElements('classnames of partially checked disabled checkbox',
      ['goog-checkbox',
       'goog-checkbox-undetermined',
       'goog-checkbox-disabled'],
      goog.dom.classlist.get(checkbox.getElement()));
}

function testIsEnabled() {
  assertTrue('enabled by default', checkbox.isEnabled());
  checkbox.setEnabled(false);
  assertFalse('has been disabled', checkbox.isEnabled());
}

function testCheckedState() {
  assertTrue('unchecked by default', !checkbox.isChecked() &&
      checkbox.isUnchecked() && !checkbox.isUndetermined());

  checkbox.setChecked(true);
  assertTrue('set to checked', checkbox.isChecked() &&
      !checkbox.isUnchecked() && !checkbox.isUndetermined());

  checkbox.setChecked(null);
  assertTrue('set to partially checked', !checkbox.isChecked() &&
      !checkbox.isUnchecked() && checkbox.isUndetermined());
}

function testToggle() {
  checkbox.setChecked(null);
  checkbox.toggle();
  assertTrue('undetermined -> checked', checkbox.getChecked());
  checkbox.toggle();
  assertFalse('checked -> unchecked', checkbox.getChecked());
  checkbox.toggle();
  assertTrue('unchecked -> checked', checkbox.getChecked());
}

function testEvents() {
  checkbox.render();

  var events = [];
  goog.events.listen(checkbox,
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK,
        goog.ui.Component.EventType.UNCHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      function(e) {
        events.push(e.type);
      });

  checkbox.setEnabled(false);
  goog.testing.events.fireClickSequence(checkbox.getElement());
  assertArrayEquals('disabled => no events', [], events);
  assertFalse('checked state did not change', checkbox.getChecked());
  events = [];

  checkbox.setEnabled(true);
  goog.testing.events.fireClickSequence(checkbox.getElement());
  assertArrayEquals('ACTION+CHECK+CHANGE fired',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      events);
  assertTrue('checkbox became checked', checkbox.getChecked());
  events = [];

  goog.testing.events.fireClickSequence(checkbox.getElement());
  assertArrayEquals('ACTION+UNCHECK+CHANGE fired',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.UNCHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      events);
  assertFalse('checkbox became unchecked', checkbox.getChecked());
  events = [];

  goog.events.listen(checkbox, goog.ui.Component.EventType.CHECK,
      function(e) {
        e.preventDefault();
      });
  goog.testing.events.fireClickSequence(checkbox.getElement());
  assertArrayEquals('ACTION+CHECK fired',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK
      ],
      events);
  assertFalse('toggling has been prevented', checkbox.getChecked());
}

function testCheckboxAriaLabelledby() {
  var label = goog.dom.createElement('div');
  var label2 = goog.dom.createElement('div', {id: checkbox.makeId('foo')});
  document.body.appendChild(label);
  document.body.appendChild(label2);
  try {
    checkbox.setChecked(false);
    checkbox.setLabel(label);
    checkbox.render(label);
    assertNotNull(checkbox.getElement());
    assertEquals(label.id,
        goog.a11y.aria.getState(checkbox.getElement(),
            goog.a11y.aria.State.LABELLEDBY));

    checkbox.setLabel(label2);
    assertEquals(label2.id,
        goog.a11y.aria.getState(checkbox.getElement(),
            goog.a11y.aria.State.LABELLEDBY));
  } finally {
    document.body.removeChild(label);
    document.body.removeChild(label2);
  }
}

function testLabel() {
  var label = goog.dom.createElement('div');
  document.body.appendChild(label);
  try {
    checkbox.setChecked(false);
    checkbox.setLabel(label);
    checkbox.render(label);

    // Clicking on label toggles checkbox.
    goog.testing.events.fireClickSequence(label);
    assertTrue('checkbox toggled if the label is clicked',
        checkbox.getChecked());
    goog.testing.events.fireClickSequence(checkbox.getElement());
    assertFalse('checkbox toggled if it is clicked', checkbox.getChecked());

    // Test that mouse events on the label have the correct effect on the
    // checkbox state when it is enabled.
    checkbox.setEnabled(true);
    goog.testing.events.fireMouseOverEvent(label);
    assertTrue(checkbox.hasState(goog.ui.Component.State.HOVER));
    assertContains('checkbox gets hover state on mouse over',
        'goog-checkbox-hover', goog.dom.classlist.get(checkbox.getElement()));
    goog.testing.events.fireMouseDownEvent(label);
    assertTrue(checkbox.hasState(goog.ui.Component.State.ACTIVE));
    assertContains('checkbox gets active state on label mousedown',
        'goog-checkbox-active',
        goog.dom.classlist.get(checkbox.getElement()));
    goog.testing.events.fireMouseOutEvent(checkbox.getElement());
    assertFalse(checkbox.hasState(goog.ui.Component.State.HOVER));
    assertNotContains('checkbox does not have hover state after mouse out',
        'goog-checkbox-hover', goog.dom.classlist.get(checkbox.getElement()));
    assertFalse(checkbox.hasState(goog.ui.Component.State.ACTIVE));
    assertNotContains('checkbox does not have active state after mouse out',
        'goog-checkbox-active', goog.dom.classlist.get(checkbox.getElement()));

    // Test label mouse events on disabled checkbox.
    checkbox.setEnabled(false);
    goog.testing.events.fireMouseOverEvent(label);
    assertFalse(checkbox.hasState(goog.ui.Component.State.HOVER));
    assertNotContains(
        'disabled checkbox does not get hover state on mouseover',
        'goog-checkbox-hover', goog.dom.classlist.get(checkbox.getElement()));
    goog.testing.events.fireMouseDownEvent(label);
    assertFalse(checkbox.hasState(goog.ui.Component.State.ACTIVE));
    assertNotContains('disabled checkbox does not get active state mousedown',
        'goog-checkbox-active',
        goog.dom.classlist.get(checkbox.getElement()));
    goog.testing.events.fireMouseOutEvent(checkbox.getElement());
    assertFalse(checkbox.hasState(goog.ui.Component.State.ACTIVE));
    assertNotContains('checkbox does not get stuck in hover state',
        'goog-checkbox-hover', goog.dom.classlist.get(checkbox.getElement()));

    // Making the label null prevents it from affecting checkbox state.
    checkbox.setEnabled(true);
    checkbox.setLabel(null);
    goog.testing.events.fireClickSequence(label);
    assertFalse('label element deactivated', checkbox.getChecked());
    goog.testing.events.fireClickSequence(checkbox.getElement());
    assertTrue('checkbox still active', checkbox.getChecked());
  } finally {
    document.body.removeChild(label);
  }
}

function testConstructor() {
  assertEquals('state is unchecked', goog.ui.Checkbox.State.UNCHECKED,
      checkbox.getChecked());

  var testCheckboxWithState = new goog.ui.Checkbox(
      goog.ui.Checkbox.State.UNDETERMINED);
  assertNotNull('checkbox created with custom state', testCheckboxWithState);
  assertEquals('checkbox state is undetermined',
      goog.ui.Checkbox.State.UNDETERMINED,
      testCheckboxWithState.getChecked());
  testCheckboxWithState.dispose();
}

function testCustomRenderer() {
  var cssClass = 'my-custom-checkbox';
  var renderer = goog.ui.ControlRenderer.getCustomRenderer(
      goog.ui.CheckboxRenderer, cssClass);
  var customCheckbox = new goog.ui.Checkbox(
      undefined, undefined, renderer);
  customCheckbox.createDom();
  assertElementsEquals(
      ['my-custom-checkbox', 'my-custom-checkbox-unchecked'],
      goog.dom.classlist.get(customCheckbox.getElement()));
  customCheckbox.setChecked(true);
  assertElementsEquals(
      ['my-custom-checkbox', 'my-custom-checkbox-checked'],
      goog.dom.classlist.get(customCheckbox.getElement()));
  customCheckbox.setChecked(null);
  assertElementsEquals(
      ['my-custom-checkbox', 'my-custom-checkbox-undetermined'],
      goog.dom.classlist.get(customCheckbox.getElement()));
  customCheckbox.dispose();
}

function testGetAriaRole() {
  checkbox.createDom();
  assertNotNull(checkbox.getElement());
  assertEquals("Checkbox's ARIA role should be 'checkbox'",
      goog.a11y.aria.Role.CHECKBOX,
      goog.a11y.aria.getRole(checkbox.getElement()));
}

function testCreateDomUpdateAriaState() {
  checkbox.createDom();
  assertNotNull(checkbox.getElement());
  assertEquals('Checkbox must have default false ARIA state aria-checked',
      'false', goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.CHECKED);
  assertEquals('Checkbox must have true ARIA state aria-checked', 'true',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.UNCHECKED);
  assertEquals('Checkbox must have false ARIA state aria-checked', 'false',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.UNDETERMINED);
  assertEquals('Checkbox must have mixed ARIA state aria-checked', 'mixed',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));
}

function testDecorateUpdateAriaState() {
  var decorateSpan = goog.dom.getElement('decorate');
  checkbox.decorate(decorateSpan);

  assertEquals('Checkbox must have default false ARIA state aria-checked',
      'false', goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.CHECKED);
  assertEquals('Checkbox must have true ARIA state aria-checked', 'true',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.UNCHECKED);
  assertEquals('Checkbox must have false ARIA state aria-checked', 'false',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));

  checkbox.setChecked(goog.ui.Checkbox.State.UNDETERMINED);
  assertEquals('Checkbox must have mixed ARIA state aria-checked', 'mixed',
      goog.a11y.aria.getState(checkbox.getElement(),
          goog.a11y.aria.State.CHECKED));
}

function testSpaceKey() {
  var normalSpan = goog.dom.getElement('normal');

  checkbox.decorate(normalSpan);
  assertEquals('default state is unchecked',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.SPACE);
  assertEquals('SPACE toggles checkbox to be checked',
      goog.ui.Checkbox.State.CHECKED, checkbox.getChecked());
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.SPACE);
  assertEquals('another SPACE toggles checkbox to be unchecked',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());

  // Enter for example doesn't work
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.ENTER);
  assertEquals('Enter leaves checkbox unchecked',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());
}

function testSpaceKeyFiresEvents() {
  var normalSpan = goog.dom.getElement('normal');

  checkbox.decorate(normalSpan);
  var events = [];
  goog.events.listen(checkbox,
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK,
        goog.ui.Component.EventType.UNCHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      function(e) {
        events.push(e.type);
      });

  assertEquals('Unexpected default state.',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.SPACE);
  assertArrayEquals('Unexpected events fired when checking with spacebar.',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      events);
  assertEquals('Unexpected state after checking.',
      goog.ui.Checkbox.State.CHECKED, checkbox.getChecked());

  events = [];
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.SPACE);
  assertArrayEquals('Unexpected events fired when unchecking with spacebar.',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.UNCHECK,
        goog.ui.Component.EventType.CHANGE
      ],
      events);
  assertEquals('Unexpected state after unchecking.',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());

  events = [];
  goog.events.listenOnce(checkbox, goog.ui.Component.EventType.CHECK,
      function(e) {
        e.preventDefault();
      });
  goog.testing.events.fireKeySequence(normalSpan, goog.events.KeyCodes.SPACE);
  assertArrayEquals('Unexpected events fired when checking with spacebar and ' +
      'the check event is cancelled.',
      [
        goog.ui.Component.EventType.ACTION,
        goog.ui.Component.EventType.CHECK
      ],
      events);
  assertEquals('Unexpected state after check event is cancelled.',
      goog.ui.Checkbox.State.UNCHECKED, checkbox.getChecked());
}

function testDecorate() {
  var normalSpan = goog.dom.getElement('normal');
  var checkedSpan = goog.dom.getElement('checked');
  var uncheckedSpan = goog.dom.getElement('unchecked');
  var undeterminedSpan = goog.dom.getElement('undetermined');
  var disabledSpan = goog.dom.getElement('disabled');

  validateCheckBox(normalSpan, goog.ui.Checkbox.State.UNCHECKED);
  validateCheckBox(checkedSpan, goog.ui.Checkbox.State.CHECKED);
  validateCheckBox(uncheckedSpan, goog.ui.Checkbox.State.UNCHECKED);
  validateCheckBox(undeterminedSpan, goog.ui.Checkbox.State.UNDETERMINED);
  validateCheckBox(disabledSpan, goog.ui.Checkbox.State.UNCHECKED, true);
}

function validateCheckBox(span, state, opt_disabled) {
  var testCheckbox = goog.ui.decorate(span);
  assertNotNull('checkbox created', testCheckbox);
  assertEquals('decorate was successful',
      goog.ui.Checkbox, testCheckbox.constructor);
  assertEquals('checkbox state should be: ' + state, state,
      testCheckbox.getChecked());
  assertEquals('checkbox is ' + (!opt_disabled ? 'enabled' : 'disabled'),
      !opt_disabled, testCheckbox.isEnabled());
  testCheckbox.dispose();
}

function testSetAriaLabel() {
  assertNull('Checkbox must not have aria label by default',
      checkbox.getAriaLabel());
  checkbox.setAriaLabel('Checkbox 1');
  checkbox.render();
  var el = checkbox.getElementStrict();
  assertEquals('Checkbox element must have expected aria-label', 'Checkbox 1',
      el.getAttribute('aria-label'));
  assertEquals('Checkbox element must have expected aria-role', 'checkbox',
      el.getAttribute('role'));
  checkbox.setAriaLabel('Checkbox 2');
  assertEquals('Checkbox element must have updated aria-label', 'Checkbox 2',
      el.getAttribute('aria-label'));
  assertEquals('Checkbox element must have expected aria-role', 'checkbox',
      el.getAttribute('role'));
}
