// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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.

'use strict';

const assert = require('assert');
const promise = require('../..').promise;
const {enablePromiseManager} = require('../../lib/test/promise');


var test = require('../../testing');

describe('Mocha Integration', function() {

  describe('beforeEach properly binds "this"', function() {
    beforeEach(function() { this.x = 1; });
    test.beforeEach(function() { this.x = 2; });
    it('', function() { assert.equal(this.x, 2); });
  });

  describe('afterEach properly binds "this"', function() {
    it('', function() { this.x = 1; });
    test.afterEach(function() { this.x = 2; });
    afterEach(function() { assert.equal(this.x, 2); });
  });

  describe('it properly binds "this"', function() {
    beforeEach(function() { this.x = 1; });
    test.it('', function() { this.x = 2; });
    afterEach(function() { assert.equal(this.x, 2); });
  });

  enablePromiseManager(function() {
    describe('timeout handling', function() {
      describe('it does not reset the control flow on a non-timeout', function() {
        var flowReset = false;

        beforeEach(function() {
          flowReset = false;
          test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset);
        });

        test.it('', function() {
          this.timeout(100);
          return promise.delayed(50);
        });

        afterEach(function() {
          assert.ok(!flowReset);
          test.controlFlow().removeListener(
              promise.ControlFlow.EventType.RESET, onreset);
        });

        function onreset() {
          flowReset = true;
        }
      });

      describe('it resets the control flow after a timeout' ,function() {
        var timeoutErr, flowReset;

        beforeEach(function() {
          flowReset = false;
          test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset);
        });

        test.it('', function() {
          var callback = this.runnable().callback;
          var test = this;
          this.runnable().callback = function(err) {
            timeoutErr = err;
            // Reset our timeout to 0 so Mocha does not fail the test.
            test.timeout(0);
            // When we invoke the real callback, do not pass along the error so
            // Mocha does not fail the test.
            return callback.call(this);
          };

          test.timeout(50);
          return promise.defer().promise;
        });

        afterEach(function() {
          return Promise.resolve().then(function() {
            test.controlFlow().removeListener(
                promise.ControlFlow.EventType.RESET, onreset);
            assert.ok(flowReset, 'control flow was not reset after a timeout');
          });
        });

        function onreset() {
          flowReset = true;
        }
      });
    });

    describe('async "done" support', function() {
       this.timeout(2*1000);

       var waited = false;
       var DELAY = 100; // ms enough to notice

       // Each test asynchronously sets waited to true, so clear/check waited
       // before/after:
       beforeEach(function() {
          waited = false;
       });

       afterEach(function() {
          assert.strictEqual(waited, true);
       });

       // --- First, vanilla mocha "it" should support the "done" callback correctly.

       // This 'it' should block until 'done' is invoked
       it('vanilla delayed', function(done) {
          setTimeout(function delayedVanillaTimeout() {
             waited = true;
             done();
          }, DELAY);
       });

       // --- Now with the webdriver wrappers for 'it' should support the "done" callback:

       test.it('delayed', function(done) {
          assert(done);
          assert.strictEqual(typeof done, 'function');
          setTimeout(function delayedTimeoutCallback() {
             waited = true;
             done();
          }, DELAY);
       });

       // --- And test that the webdriver wrapper for 'it' works with a returned promise, too:

       test.it('delayed by promise', function() {
          var defer = promise.defer();
          setTimeout(function delayedPromiseCallback() {
             waited = true;
             defer.fulfill('ignored');
          });
          return defer.promise;
       });
    });

    describe('ControlFlow and "done" work together', function() {
       var flow, order;
       before(function() {
          order = [];
          flow = test.controlFlow();
          flow.execute(function() { order.push(1); });
       });

       test.it('control flow updates and async done', function(done) {
          flow.execute(function() { order.push(2); });
          flow.execute(function() { order.push(3); }).then(function() {
             order.push(4);
          });
          done();
       });

       after(function() {
          assert.deepEqual([1, 2, 3, 4], order);
       });
    });
  });

  describe('generator support', function() {
    let arr;

    beforeEach(() => arr = []);
    afterEach(() => assert.deepEqual(arr, [0, 1, 2, 3]));

    test.it('sync generator', function* () {
      arr.push(yield arr.length);
      arr.push(yield arr.length);
      arr.push(yield arr.length);
      arr.push(yield arr.length);
    });

    test.it('async generator', function* () {
      arr.push(yield Promise.resolve(arr.length));
      arr.push(yield Promise.resolve(arr.length));
      arr.push(yield Promise.resolve(arr.length));
      arr.push(yield Promise.resolve(arr.length));
    });

    test.it('generator returns promise', function*() {
      arr.push(yield Promise.resolve(arr.length));
      arr.push(yield Promise.resolve(arr.length));
      arr.push(yield Promise.resolve(arr.length));
      setTimeout(_ => arr.push(arr.length), 10);
      return new Promise((resolve) => setTimeout(_ => resolve(), 25));
    });

    describe('generator runs with proper "this" context', () => {
      before(function() { this.values = [0, 1, 2, 3]; });
      test.it('', function*() {
        arr = this.values;
      });
    });

    it('generator function must not take a callback', function() {
      arr = [0, 1, 2, 3];  // For teardown hook.
      assert.throws(_ => {
        test.it('', function*(done){});
      }, TypeError);
    });
  });
});
