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

local function testPromiseResolution(xFactory, test)
    it('via return from a fulfilled promise', function()
        local p = promise.resolve(dummy):thenCall(function()
            return xFactory()
        end)
        test(p)
        assert.True(wait())
    end)

    it('via return from a rejected promise', function()
        local p = promise.reject(dummy):thenCall(nil, function()
            return xFactory()
        end)
        test(p)
        assert.True(wait())
    end)
end

describe('2.3.2: If `x` is a promise, adopt its state', function()
    describe('2.3.2.1: If `x` is pending, `promise` must remain pending until `x` is ' ..
        'fulfilled or rejected.', function()
        testPromiseResolution(function()
            return deferredPromise()
        end, function(p)
            local onFulfilled = spy.new(function() end)
            local onRejected = spy.new(function() end)
            p:thenCall(onFulfilled, onRejected)

            assert.spy(onFulfilled).was_not_called()
            assert.spy(onRejected).was_not_called()
            done()
        end)
    end)

    describe('2.3.2.2: If/when `x` is fulfilled, fulfill `promise` with the same value.', function()
        describe('`x` is already-fulfilled', function()
            testPromiseResolution(function()
                return promise.resolve(sentinel)
            end, function(p)
                p:thenCall(function(value)
                    assert.equal(sentinel, value)
                    done()
                end)
            end)
        end)

        describe('`x` is eventually-fulfilled', function()
            testPromiseResolution(function()
                local p, resolve = deferredPromise()
                setTimeout(function()
                    resolve(sentinel)
                end, 10)
                return p
            end, function(p)
                p:thenCall(function(value)
                    assert.equal(sentinel, value)
                    done()
                end)
            end)
        end)
    end)

    describe('2.3.2.3: If/when `x` is rejected, reject `promise` with the same reason.', function()
        describe('`x` is already-rejected', function()
            testPromiseResolution(function()
                return promise.reject(sentinel)
            end, function(p)
                p:thenCall(nil, function(reason)
                    assert.equal(sentinel, reason)
                    done()
                end)
            end)
        end)

        describe('`x` is eventually-rejected', function()
            testPromiseResolution(function()
                local p, _, reject = deferredPromise()
                setTimeout(function()
                    reject(sentinel)
                end, 10)
                return p
            end, function(p)
                p:thenCall(nil, function(reason)
                    assert.equal(sentinel, reason)
                    done()
                end)
            end)
        end)
    end)
end)
