/**
 * @license
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.
 */

import '../../../test/common-test-setup-karma.js';
import './gr-file-list-header.js';
import {FilesExpandedState} from '../gr-file-list-constants.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
import 'lodash/lodash.js';
import {createRevisions} from '../../../test/test-data-generators.js';

const basicFixture = fixtureFromElement('gr-file-list-header');

suite('gr-file-list-header tests', () => {
  let element;

  setup(() => {
    stub('gr-rest-api-interface', {
      getConfig() { return Promise.resolve({test: 'config'}); },
      getAccount() { return Promise.resolve(null); },
      _fetchSharedCacheURL() { return Promise.resolve({}); },
    });
    element = basicFixture.instantiate();
  });

  teardown(done => {
    flush(() => {
      done();
    });
  });

  test('Diff preferences hidden when no prefs or diffPrefsDisabled', () => {
    element.diffPrefsDisabled = true;
    flush();
    assert.isTrue(element.$.diffPrefsContainer.hidden);

    element.diffPrefsDisabled = false;
    flush();
    assert.isTrue(element.$.diffPrefsContainer.hidden);

    element.diffPrefsDisabled = true;
    element.diffPrefs = {font_size: '12'};
    flush();
    assert.isTrue(element.$.diffPrefsContainer.hidden);

    element.diffPrefsDisabled = false;
    flush();
    assert.isFalse(element.$.diffPrefsContainer.hidden);
  });

  test('_computeDescriptionReadOnly', () => {
    element.loggedIn = false;
    element.change = {owner: {_account_id: 1}};
    element.account = {_account_id: 1};
    assert.equal(element._descriptionReadOnly, true);

    element.loggedIn = true;
    element.change = {owner: {_account_id: 0}};
    element.account = {_account_id: 1};
    assert.equal(element._descriptionReadOnly, true);

    element.loggedIn = true;
    element.change = {owner: {_account_id: 1}};
    element.account = {_account_id: 1};
    assert.equal(element._descriptionReadOnly, false);
  });

  test('_computeDescriptionPlaceholder', () => {
    assert.equal(element._computeDescriptionPlaceholder(true),
        'No patchset description');
    assert.equal(element._computeDescriptionPlaceholder(false),
        'Add patchset description');
  });

  test('description editing', () => {
    const putDescStub = sinon.stub(element.$.restAPI, 'setDescription')
        .returns(Promise.resolve({ok: true}));

    element.changeNum = '42';
    element.basePatchNum = 'PARENT';
    element.patchNum = 1;

    element.change = {
      change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
      revisions: {
        rev1: {_number: 1, description: 'test', commit: {commit: 'rev1'}},
      },
      current_revision: 'rev1',
      status: 'NEW',
      labels: {},
      actions: {},
      owner: {_account_id: 1},
    };
    element.account = {_account_id: 1};
    element.owner = {_account_id: 1};
    element.loggedIn = true;

    flush();

    // The element has a description, so the account chip should be visible
    element.owner = {_account_id: 1};
    // and the description label should not exist.
    const chip = element.root.querySelector('#descriptionChip');
    let label = element.root.querySelector('#descriptionLabel');

    assert.equal(chip.text, 'test');
    assert.isNotOk(label);

    // Simulate tapping the remove button, but call function directly so that
    // can determine what happens after the promise is resolved.
    return element._handleDescriptionRemoved()
        .then(() => {
          // The API stub should be called with an empty string for the new
          // description.
          assert.equal(putDescStub.lastCall.args[2], '');
          assert.equal(element.change.revisions.rev1.description, '');

          flush();
          // The editable label should now be visible and the chip hidden.
          label = element.root.querySelector('#descriptionLabel');
          assert.isOk(label);
          assert.equal(getComputedStyle(chip).display, 'none');
          assert.notEqual(getComputedStyle(label).display, 'none');
          assert.isFalse(label.readOnly);
          // Edit the label to have a new value of test2, and save.
          label.editing = true;
          label._inputText = 'test2';
          label._save();
          flush();
          // The API stub should be called with an `test2` for the new
          // description.
          assert.equal(putDescStub.callCount, 2);
          assert.equal(putDescStub.lastCall.args[2], 'test2');
        })
        .then(() => {
          flush();
          // The chip should be visible again, and the label hidden.
          assert.equal(element.change.revisions.rev1.description, 'test2');
          assert.equal(getComputedStyle(label).display, 'none');
          assert.notEqual(getComputedStyle(chip).display, 'none');
        });
  });

  test('expandAllDiffs called when expand button clicked', () => {
    element.shownFileCount = 1;
    flush();
    sinon.stub(element, '_expandAllDiffs');
    MockInteractions.tap(element.root.querySelector(
        '#expandBtn'));
    assert.isTrue(element._expandAllDiffs.called);
  });

  test('collapseAllDiffs called when expand button clicked', () => {
    element.shownFileCount = 1;
    flush();
    sinon.stub(element, '_collapseAllDiffs');
    MockInteractions.tap(element.root.querySelector(
        '#collapseBtn'));
    assert.isTrue(element._collapseAllDiffs.called);
  });

  test('show/hide diffs disabled for large amounts of files', done => {
    const computeSpy = sinon.spy(element, '_fileListActionsVisible');
    element._files = [];
    element.changeNum = '42';
    element.basePatchNum = 'PARENT';
    element.patchNum = '2';
    element.shownFileCount = 1;
    flush(() => {
      assert.isTrue(computeSpy.lastCall.returnValue);
      _.times(element._maxFilesForBulkActions + 1, () => {
        element.shownFileCount = element.shownFileCount + 1;
      });
      assert.isFalse(computeSpy.lastCall.returnValue);
      done();
    });
  });

  test('fileViewActions are properly hidden', () => {
    const actions = element.shadowRoot
        .querySelector('.fileViewActions');
    assert.equal(getComputedStyle(actions).display, 'none');
    element.filesExpanded = FilesExpandedState.SOME;
    flush();
    assert.notEqual(getComputedStyle(actions).display, 'none');
    element.filesExpanded = FilesExpandedState.ALL;
    flush();
    assert.notEqual(getComputedStyle(actions).display, 'none');
    element.filesExpanded = FilesExpandedState.NONE;
    flush();
    assert.equal(getComputedStyle(actions).display, 'none');
  });

  test('expand/collapse buttons are toggled correctly', () => {
    element.shownFileCount = 10;
    flush();
    const expandBtn = element.shadowRoot.querySelector('#expandBtn');
    const collapseBtn = element.shadowRoot.querySelector('#collapseBtn');
    assert.notEqual(getComputedStyle(expandBtn).display, 'none');
    assert.equal(getComputedStyle(collapseBtn).display, 'none');
    element.filesExpanded = FilesExpandedState.SOME;
    flush();
    assert.notEqual(getComputedStyle(expandBtn).display, 'none');
    assert.equal(getComputedStyle(collapseBtn).display, 'none');
    element.filesExpanded = FilesExpandedState.ALL;
    flush();
    assert.equal(getComputedStyle(expandBtn).display, 'none');
    assert.notEqual(getComputedStyle(collapseBtn).display, 'none');
    element.filesExpanded = FilesExpandedState.NONE;
    flush();
    assert.notEqual(getComputedStyle(expandBtn).display, 'none');
    assert.equal(getComputedStyle(collapseBtn).display, 'none');
  });

  test('navigateToChange called when range select changes', () => {
    const navigateToChangeStub = sinon.stub(GerritNav, 'navigateToChange');
    element.change = {
      change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
      revisions: {
        rev2: {_number: 2},
        rev1: {_number: 1},
        rev13: {_number: 13},
        rev3: {_number: 3},
      },
      status: 'NEW',
      labels: {},
    };
    element.basePatchNum = 1;
    element.patchNum = 2;

    element._handlePatchChange({detail: {basePatchNum: 1, patchNum: 3}});
    assert.equal(navigateToChangeStub.callCount, 1);
    assert.isTrue(navigateToChangeStub.lastCall
        .calledWithExactly(element.change, 3, 1));
  });

  test('class is applied to file list on old patch set', () => {
    const allPatchSets = [{num: 4}, {num: 2}, {num: 1}];
    assert.equal(element._computePatchInfoClass('1', allPatchSets),
        'patchInfoOldPatchSet');
    assert.equal(element._computePatchInfoClass('2', allPatchSets),
        'patchInfoOldPatchSet');
    assert.equal(element._computePatchInfoClass('4', allPatchSets), '');
  });

  suite('editMode behavior', () => {
    setup(() => {
      element.diffPrefsDisabled = false;
      element.diffPrefs = {};
    });

    const isVisible = el => {
      assert.ok(el);
      return getComputedStyle(el).getPropertyValue('display') !== 'none';
    };

    test('patch specific elements', () => {
      element.editMode = true;
      element.allPatchSets = createRevisions(2);
      flush();

      assert.isFalse(isVisible(element.$.diffPrefsContainer));
      assert.isFalse(isVisible(element.shadowRoot
          .querySelector('.descriptionContainer')));

      element.editMode = false;
      flush();

      assert.isTrue(isVisible(element.shadowRoot
          .querySelector('.descriptionContainer')));
      assert.isTrue(isVisible(element.$.diffPrefsContainer));
    });

    test('edit-controls visibility', () => {
      element.editMode = false;
      flush();
      // on the first render, when editMode is false, editControls are not
      // in the DOM to reduce size of DOM and make first render faster.
      assert.isNull(element.shadowRoot
          .querySelector('#editControls'));

      element.editMode = true;
      flush();
      assert.isTrue(isVisible(element.shadowRoot
          .querySelector('#editControls').parentElement));

      element.editMode = false;
      flush();
      assert.isFalse(isVisible(element.shadowRoot
          .querySelector('#editControls').parentElement));
    });

    test('_computeUploadHelpContainerClass', () => {
      // Only show the upload helper button when an unmerged change is viewed
      // by its owner.
      const accountA = {_account_id: 1};
      const accountB = {_account_id: 2};
      assert.notInclude(element._computeUploadHelpContainerClass(
          {owner: accountA}, accountA), 'hide');
      assert.include(element._computeUploadHelpContainerClass(
          {owner: accountA}, accountB), 'hide');
    });
  });
});

