local helpers = require('spec.helpers.init')
local testFulfilled = helpers.testFulfilled
local testRejected = helpers.testRejected
local setTimeout = helpers.setTimeout
local deferredPromise = helpers.deferredPromise
local promise = require('promise')
local dummy = {dummy = 'dummy'}

describe('2.2.4: `onFulfilled` or `onRejected` must not be called until ' ..
    'the execution context stack contains only platform code.', function()
    describe('`thenCall` returns before the promise becomes fulfilled or rejected', function()
        testFulfilled(it, assert, dummy, function(p)
            local onFulfilled = spy.new(done)
            p:thenCall(onFulfilled)
        end)

        testRejected(it, assert, dummy, function(p)
            local onRejected = spy.new(done)
            p:thenCall(nil, onRejected)
        end)
    end)

    describe('Clean-stack execution ordering tests (fulfillment case)', function()
        local onFulfilled = spy.new(done)

        before_each(function()
            onFulfilled:clear()
        end)

        it('when `onFulfilled` is added immediately before the promise is fulfilled', function()
            local p, resolve = deferredPromise()
            p:thenCall(onFulfilled)
            resolve(dummy)
            assert.True(wait())
            assert.spy(onFulfilled).was_called(1)
        end)

        it('when `onFulfilled` is added immediately after the promise is fulfilled', function()
            local p, resolve = deferredPromise()
            resolve(dummy)
            p:thenCall(onFulfilled)
            assert.True(wait())
            assert.spy(onFulfilled).was_called(1)
        end)

        it('when one `onFulfilled` is added inside another `onFulfilled`', function()
            local p = promise.resolve()
            p:thenCall(function()
                p:thenCall(onFulfilled)
            end)
            assert.True(wait())
            assert.spy(onFulfilled).was_called(1)
        end)

        it('when `onFulfilled` is added inside an `onRejected`', function()
            local p1 = promise.reject()
            local p2 = promise.resolve()
            p1:thenCall(nil, function()
                p2:thenCall(onFulfilled)
            end)
            assert.True(wait())
            assert.spy(onFulfilled).was_called(1)
        end)

        it('when the promise is fulfilled asynchronously', function()
            local p, resolve = deferredPromise()
            setTimeout(function()
                resolve(dummy)
            end, 0)
            p:thenCall(onFulfilled)
            assert.True(wait())
            assert.spy(onFulfilled).was_called(1)
        end)
    end)

    describe('Clean-stack execution ordering tests (rejection case)', function()
        local onRejected = spy.new(done)

        before_each(function()
            onRejected:clear()
        end)

        it('when `onRejected` is added immediately before the promise is rejected', function()
            local p, _, reject = deferredPromise()
            p:thenCall(nil, onRejected)
            reject(dummy)
            assert.True(wait())
            assert.spy(onRejected).was_called(1)
        end)

        it('when `onRejected` is added immediately after the promise is rejected', function()
            local p, _, reject = deferredPromise()
            reject(dummy)
            p:thenCall(nil, onRejected)
            assert.True(wait())
            assert.spy(onRejected).was_called(1)
        end)

        it('when `onRejected` is added inside an `onFulfilled`', function()
            local p1 = promise.resolve()
            local p2 = promise.reject()
            p1:thenCall(function()
                p2:thenCall(nil, onRejected)
            end)
            assert.True(wait())
            assert.spy(onRejected).was_called(1)
        end)

        it('when one `onRejected` is added inside another `onRejected`', function()
            local p = promise.reject()
            p:thenCall(nil, function()
                p:thenCall(nil, onRejected)
            end)
            assert.True(wait())
            assert.spy(onRejected).was_called(1)
        end)

        it('when the promise is rejected asynchronously', function()
            local p, _, reject = deferredPromise()
            setTimeout(function()
                reject(dummy)
            end, 0)
            p:thenCall(nil, onRejected)
            assert.True(wait())
            assert.spy(onRejected).was_called(1)
        end)
    end)
end)
