Regenerate nvim config
This commit is contained in:
@ -0,0 +1,406 @@
|
||||
local promise = require('promise')
|
||||
local async = require('async')
|
||||
local helpers = require('spec.helpers.init')
|
||||
local compat = require('promise-async.compat')
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local setTimeout = helpers.setTimeout
|
||||
local basics = require('spec.helpers.basics')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
local sentinel2 = {sentinel = 'sentinel2'}
|
||||
local sentinel3 = {sentinel = 'sentinel3'}
|
||||
local other = {other = 'other'}
|
||||
|
||||
describe('async await module.', function()
|
||||
describe('async return a Promise.', function()
|
||||
it('return value is a Promise', function()
|
||||
local f = async(function() end)
|
||||
assert.True(promise.isInstance(f))
|
||||
end)
|
||||
|
||||
it('async without return statement', function()
|
||||
async(function() end)
|
||||
:thenCall(function(value)
|
||||
assert.equal(nil, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('async return a single', function()
|
||||
async(function()
|
||||
return dummy
|
||||
end):thenCall(function(value)
|
||||
assert.equal(dummy, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('async return multiple values, which are packed into resolved result in Promise', function()
|
||||
async(function()
|
||||
return sentinel, sentinel2, sentinel3
|
||||
end):thenCall(function(value)
|
||||
assert.same({sentinel, sentinel2, sentinel3}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('async throw error', function()
|
||||
async(function()
|
||||
error(dummy)
|
||||
return other
|
||||
end):thenCall(nil, function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('executor inside async.', function()
|
||||
describe('must be either a function or a callable table,', function()
|
||||
it('other values', function()
|
||||
local errorValues = {nil, 0, '0', true}
|
||||
for _, v in pairs(errorValues) do
|
||||
assert.error(function()
|
||||
async(v)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
it('a function', function()
|
||||
async(function()
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 10)
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('a callable table', function()
|
||||
async(setmetatable({}, {
|
||||
__call = function()
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 10)
|
||||
end
|
||||
}))
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should run immediately', function()
|
||||
local executor = spy.new(function() end)
|
||||
async(executor)
|
||||
assert.spy(executor).was_called()
|
||||
end)
|
||||
|
||||
it('until the await is called, executor should run immediately even if in nested function', function()
|
||||
local value
|
||||
local executor = spy.new(function() end)
|
||||
async(function()
|
||||
value = async.wait(async(function()
|
||||
return async(function()
|
||||
executor()
|
||||
return dummy
|
||||
end)
|
||||
end))
|
||||
done()
|
||||
end)
|
||||
assert.spy(executor).was_called()
|
||||
assert.True(wait())
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe([[await inside async's executor.]], function()
|
||||
local function testBasicAwait(expectedValue, stringRepresentation)
|
||||
it('should wait for the promise with resolved value: ' .. stringRepresentation, function()
|
||||
local value
|
||||
local p, resolve = deferredPromise()
|
||||
async(function()
|
||||
value = await(p)
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
resolve(expectedValue)
|
||||
assert.True(wait())
|
||||
assert.equal(expectedValue, value)
|
||||
end)
|
||||
end
|
||||
|
||||
for valueStr, basicFn in pairs(basics) do
|
||||
testBasicAwait(basicFn(), valueStr)
|
||||
end
|
||||
end)
|
||||
|
||||
describe('`pcall` and `xpcall` surround statement or function.', function()
|
||||
it('call `pcall` to get the value from a single await', function()
|
||||
local ok, value
|
||||
local p, resolve = deferredPromise()
|
||||
async(function()
|
||||
ok, value = pcall(await, p)
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
resolve(dummy)
|
||||
assert.True(wait())
|
||||
assert.True(ok)
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
|
||||
it('call `xpcall` to get the value from a single await', function()
|
||||
local ok, value
|
||||
local p, resolve = deferredPromise()
|
||||
async(function()
|
||||
ok, value = xpcall(await, function() end, p)
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
resolve(dummy)
|
||||
assert.True(wait())
|
||||
assert.True(ok)
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
|
||||
it('call `pcall` to catch the reason from a single await', function()
|
||||
local ok, reason
|
||||
local p, _, reject = deferredPromise()
|
||||
async(function()
|
||||
ok, reason = pcall(await, p)
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
reject(dummy)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
|
||||
it('call `xpcall` to catch the reason from a single await', function()
|
||||
local ok, reason
|
||||
local p, _, reject = deferredPromise()
|
||||
async(function()
|
||||
ok = xpcall(await, function(e)
|
||||
reason = e
|
||||
end, p)
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
reject(dummy)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
|
||||
describe('call `pcall` to catch the reason from a function,', function()
|
||||
it('throw error after the result return by await', function()
|
||||
local ok, value, reason
|
||||
local p1, resolve = deferredPromise()
|
||||
local p2, _, reject = deferredPromise()
|
||||
async(function()
|
||||
ok, reason = pcall(function()
|
||||
value = await(p1)
|
||||
await(p2)
|
||||
end)
|
||||
done()
|
||||
end)
|
||||
p1:thenCall(function()
|
||||
reject(dummy)
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
setTimeout(function()
|
||||
resolve(other)
|
||||
end, 20)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(other, value)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
|
||||
it('throw error before the result return by await', function()
|
||||
local ok, value, reason
|
||||
local p1, _, reject = deferredPromise()
|
||||
local p2, resolve = deferredPromise()
|
||||
async(function()
|
||||
ok, reason = pcall(function()
|
||||
await(p1)
|
||||
value = await(p2)
|
||||
end)
|
||||
done()
|
||||
end)
|
||||
resolve(other)
|
||||
assert.False(wait(10))
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
end, 20)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(nil, value)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('call `xpcall` to catch the reason from a function,', function()
|
||||
it('throw error after the result return by await', function()
|
||||
local ok, value, reason
|
||||
local p1, resolve = deferredPromise()
|
||||
local p2, _, reject = deferredPromise()
|
||||
async(function()
|
||||
ok = xpcall(function()
|
||||
value = await(p1)
|
||||
await(p2)
|
||||
end, function(e)
|
||||
reason = e
|
||||
end)
|
||||
done()
|
||||
end)
|
||||
p1:thenCall(function()
|
||||
reject(dummy)
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
setTimeout(function()
|
||||
resolve(other)
|
||||
end, 20)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(other, value)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
|
||||
it('throw error before the result return by await', function()
|
||||
local ok, value, reason
|
||||
local p1, _, reject = deferredPromise()
|
||||
local p2, resolve = deferredPromise()
|
||||
async(function()
|
||||
ok = xpcall(function()
|
||||
await(p1)
|
||||
value = await(p2)
|
||||
end, function(e)
|
||||
reason = e
|
||||
end)
|
||||
done()
|
||||
end)
|
||||
resolve(other)
|
||||
assert.False(wait(10))
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
end, 20)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(nil, value)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nested async functions.', function()
|
||||
it('simple call chain', function()
|
||||
local value
|
||||
async(function()
|
||||
value = await(async(function()
|
||||
return async(function()
|
||||
return dummy
|
||||
end)
|
||||
end))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
|
||||
it('deferred call', function()
|
||||
local value
|
||||
setTimeout(function()
|
||||
async(function()
|
||||
value = await(async(function()
|
||||
return async(function()
|
||||
return sentinel
|
||||
end)
|
||||
end))
|
||||
done()
|
||||
end)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.equal(sentinel, value)
|
||||
end)
|
||||
|
||||
it('return multiple values', function()
|
||||
local value1, value2, value3, value4
|
||||
async(function()
|
||||
value1, value2, value3, value4 = await(async(function()
|
||||
return sentinel, sentinel2, sentinel3
|
||||
end))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
assert.equal(sentinel, value1)
|
||||
assert.equal(sentinel2, value2)
|
||||
assert.equal(sentinel3, value3)
|
||||
assert.equal(nil, value4)
|
||||
end)
|
||||
|
||||
it('should catch error from the deepest callee', function()
|
||||
local ok, value, reason
|
||||
async(function()
|
||||
ok = xpcall(function()
|
||||
value = await(async(function()
|
||||
return async(function()
|
||||
error(dummy)
|
||||
return other
|
||||
end)
|
||||
end))
|
||||
end, function(e)
|
||||
reason = e
|
||||
end)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
assert.False(ok)
|
||||
assert.equal(nil, value)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
|
||||
it('should wait for the deepest callee', function()
|
||||
local value
|
||||
local p, resolve = deferredPromise()
|
||||
async(function()
|
||||
value = await(async(function()
|
||||
return await(async(function()
|
||||
return await(p)
|
||||
end))
|
||||
end))
|
||||
done()
|
||||
end)
|
||||
resolve(dummy)
|
||||
assert.True(wait())
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
|
||||
it('should wait for all callees', function()
|
||||
local value
|
||||
local p1, resolve1 = deferredPromise()
|
||||
local p2, resolve2 = deferredPromise()
|
||||
local p3, resolve3 = deferredPromise()
|
||||
|
||||
async(function()
|
||||
value = compat.pack(await(async(function()
|
||||
return await(p1), await(async(function()
|
||||
return await(p2), await(p3)
|
||||
end))
|
||||
end)))
|
||||
done()
|
||||
end)
|
||||
assert.False(wait(10))
|
||||
resolve3(sentinel3)
|
||||
assert.False(wait(10))
|
||||
resolve2(sentinel2)
|
||||
assert.False(wait(10))
|
||||
resolve1(sentinel)
|
||||
assert.True(wait())
|
||||
assert.same({sentinel, sentinel2, sentinel3, n = 3}, value)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,50 @@
|
||||
local busted = require('busted')
|
||||
|
||||
local _done
|
||||
local co = _G.co
|
||||
|
||||
busted.subscribe({'test', 'start'}, function()
|
||||
_done = false
|
||||
end)
|
||||
|
||||
local function getDone()
|
||||
return _done
|
||||
end
|
||||
|
||||
function _G.done()
|
||||
_done = true
|
||||
return _done
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------
|
||||
---Need to implement _G.wait to pass tests
|
||||
|
||||
local defaultTimeout = 1000
|
||||
|
||||
---Should override this function to customize EventLoop to pass tests
|
||||
---@param ms? number
|
||||
---@return boolean
|
||||
function _G.wait(ms)
|
||||
if getDone() then
|
||||
return true
|
||||
end
|
||||
local interval = 5
|
||||
local timer = require('luv').new_timer()
|
||||
local cnt = 0
|
||||
ms = ms or defaultTimeout
|
||||
timer:start(interval, interval, function()
|
||||
cnt = cnt + interval
|
||||
local d = getDone()
|
||||
if cnt >= ms or d then
|
||||
timer:stop()
|
||||
timer:close()
|
||||
local thread = coroutine.running()
|
||||
if thread ~= co then
|
||||
require('spec.helpers.init').setTimeout(function()
|
||||
coroutine.resume(co, d)
|
||||
end, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
return coroutine.yield()
|
||||
end
|
||||
@ -0,0 +1,30 @@
|
||||
local Basic = {}
|
||||
|
||||
local co = coroutine.create(function() end)
|
||||
coroutine.resume(co)
|
||||
|
||||
Basic['`nil`'] = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
Basic['`false`'] = function()
|
||||
return false
|
||||
end
|
||||
|
||||
Basic['`0`'] = function()
|
||||
return 0
|
||||
end
|
||||
|
||||
Basic['`string`'] = function()
|
||||
return 'string'
|
||||
end
|
||||
|
||||
Basic['a metatable'] = function()
|
||||
return setmetatable({}, {})
|
||||
end
|
||||
|
||||
Basic['a thread'] = function()
|
||||
return co
|
||||
end
|
||||
|
||||
return Basic
|
||||
@ -0,0 +1,58 @@
|
||||
local M = {}
|
||||
local promise = require('promise')
|
||||
|
||||
M.setTimeout = promise.loop.setTimeout
|
||||
|
||||
function M.deferredPromise()
|
||||
local resolve, reject
|
||||
local p = promise(function(resolve0, reject0)
|
||||
resolve, reject = resolve0, reject0
|
||||
end)
|
||||
return p, resolve, reject
|
||||
end
|
||||
|
||||
function M.testFulfilled(it, assert, value, test)
|
||||
it('already-fulfilled', function()
|
||||
test(promise.resolve(value))
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('immediately-fulfilled', function()
|
||||
local p, resolve = M.deferredPromise()
|
||||
test(p)
|
||||
resolve(value)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('eventually-fulfilled', function()
|
||||
local p, resolve = M.deferredPromise()
|
||||
test(p)
|
||||
wait(10)
|
||||
resolve(value)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
function M.testRejected(it, assert, reason, test)
|
||||
it('already-rejected', function()
|
||||
test(promise.reject(reason))
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('immediately-rejected', function()
|
||||
local p, _, reject = M.deferredPromise()
|
||||
test(p)
|
||||
reject(reason)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('eventually-fulfilled', function()
|
||||
local p, _, reject = M.deferredPromise()
|
||||
test(p)
|
||||
wait(10)
|
||||
reject(reason)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,29 @@
|
||||
return function(options)
|
||||
local busted = require('busted')
|
||||
local handler = require('busted.outputHandlers.utfTerminal')(options)
|
||||
|
||||
local promiseUnhandledError = {}
|
||||
|
||||
busted.subscribe({'test', 'end'}, function(element, parent)
|
||||
while #promiseUnhandledError > 0 do
|
||||
local res = table.remove(promiseUnhandledError, 1)
|
||||
handler.successesCount = handler.successesCount - 1
|
||||
handler.failuresCount = handler.failuresCount + 1
|
||||
busted.publish({'failure', element.descriptor}, element, parent, tostring(res))
|
||||
end
|
||||
end)
|
||||
|
||||
require('promise').loop.callWrapper = function(callback)
|
||||
local ok, res = pcall(callback)
|
||||
if ok then
|
||||
return
|
||||
end
|
||||
-- Some tests never handle the rejected promises, We should ignore them.
|
||||
local msg = tostring(res)
|
||||
if msg:match('^UnhandledPromiseRejection') then
|
||||
return
|
||||
end
|
||||
table.insert(promiseUnhandledError, msg)
|
||||
end
|
||||
return handler
|
||||
end
|
||||
@ -0,0 +1,37 @@
|
||||
local promise = require('promise')
|
||||
local Reasons = {}
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
Reasons['`nil`'] = function()
|
||||
return nil
|
||||
end
|
||||
|
||||
Reasons['`false`'] = function()
|
||||
return false
|
||||
end
|
||||
|
||||
-- Lua before 5.3 versions will transfer number to string after pcall.
|
||||
-- Pure string will carry some extra information after pcall, no need to test
|
||||
-- Reasons['`0`'] = function()
|
||||
-- return 0
|
||||
-- end
|
||||
|
||||
Reasons['a metatable'] = function()
|
||||
return setmetatable({}, {})
|
||||
end
|
||||
|
||||
Reasons['an always-pending thenable'] = function()
|
||||
return {
|
||||
thenCall = function() end
|
||||
}
|
||||
end
|
||||
|
||||
Reasons['a fulfilled promise'] = function()
|
||||
return promise.resolve(dummy)
|
||||
end
|
||||
|
||||
Reasons['a rejected promise'] = function()
|
||||
return promise.reject(dummy)
|
||||
end
|
||||
|
||||
return Reasons
|
||||
@ -0,0 +1,137 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local setTimeout = helpers.setTimeout
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local promise = require('promise')
|
||||
local other = {other = 'other'}
|
||||
|
||||
local Thenables = {
|
||||
fulfilled = {
|
||||
['a synchronously-fulfilled custom thenable'] = function(value)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(value)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['an asynchronously-fulfilled custom thenable'] = function(value)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
setTimeout(function()
|
||||
resolvePromise(value)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['a synchronously-fulfilled one-time thenable'] = function(value)
|
||||
local numberOfTimesThenRetrieved = 0;
|
||||
return setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
if numberOfTimesThenRetrieved == 0 and k == 'thenCall' then
|
||||
numberOfTimesThenRetrieved = numberOfTimesThenRetrieved + 1
|
||||
return function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(value)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
})
|
||||
end,
|
||||
['a thenable that tries to fulfill twice'] = function(value)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(value)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['a thenable that fulfills but then throws'] = function(value)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(value)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['an already-fulfilled promise'] = function(value)
|
||||
return promise.resolve(value)
|
||||
end,
|
||||
['an eventually-fulfilled promise'] = function(value)
|
||||
local p, resolve = deferredPromise()
|
||||
setTimeout(function()
|
||||
resolve(value)
|
||||
end, 10)
|
||||
return p
|
||||
end
|
||||
},
|
||||
rejected = {
|
||||
['a synchronously-rejected custom thenable'] = function(reason)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(reason)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['an asynchronously-rejected custom thenable'] = function(reason)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
setTimeout(function()
|
||||
rejectPromise(reason)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['a synchronously-rejected one-time thenable'] = function(reason)
|
||||
local numberOfTimesThenRetrieved = 0;
|
||||
return setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
if numberOfTimesThenRetrieved == 0 and k == 'thenCall' then
|
||||
numberOfTimesThenRetrieved = numberOfTimesThenRetrieved + 1
|
||||
return function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(reason)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
})
|
||||
end,
|
||||
['a thenable that immediately throws in `thenCall`'] = function(reason)
|
||||
return {
|
||||
thenCall = function()
|
||||
error(reason)
|
||||
end
|
||||
}
|
||||
end,
|
||||
['an table with a throwing `thenCall` metatable'] = function(reason)
|
||||
return setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
if k == 'thenCall' then
|
||||
return function()
|
||||
error(reason)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
})
|
||||
end,
|
||||
['an already-rejected promise'] = function(reason)
|
||||
return promise.reject(reason)
|
||||
end,
|
||||
['an eventually-rejected promise'] = function(reason)
|
||||
local p, _, reject = deferredPromise()
|
||||
setTimeout(function()
|
||||
reject(reason)
|
||||
end, 10)
|
||||
return p
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
return Thenables
|
||||
54
config/neovim/store/lazy-plugins/promise-async/spec/init.lua
Normal file
54
config/neovim/store/lazy-plugins/promise-async/spec/init.lua
Normal file
@ -0,0 +1,54 @@
|
||||
package.path = os.getenv('PWD') .. '/lua/?.lua;' .. package.path
|
||||
local compat = require('promise-async.compat')
|
||||
|
||||
if compat.is51() then
|
||||
_G.pcall = compat.pcall
|
||||
_G.xpcall = compat.xpcall
|
||||
end
|
||||
|
||||
local uv = require('luv')
|
||||
|
||||
local co = coroutine.create(function()
|
||||
require('busted.runner')({standalone = false, output = 'spec.helpers.outputHandler'})
|
||||
-- no errors for nvim
|
||||
if vim then
|
||||
vim.schedule(function()
|
||||
vim.cmd('cq 0')
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
_G.co = co
|
||||
|
||||
local compatibility = require('busted.compatibility')
|
||||
|
||||
if vim then
|
||||
compatibility.exit = function(code)
|
||||
vim.schedule(function()
|
||||
vim.cmd(('cq %d'):format(code))
|
||||
end)
|
||||
end
|
||||
_G.arg = vim.fn.argv()
|
||||
_G.print = function(...)
|
||||
local argv = {...}
|
||||
for i = 1, #argv do
|
||||
argv[i] = tostring(argv[i])
|
||||
end
|
||||
table.insert(argv, '\n')
|
||||
io.write(unpack(argv))
|
||||
end
|
||||
coroutine.resume(co)
|
||||
else
|
||||
local c = 0
|
||||
-- https://github.com/luvit/luv/issues/599
|
||||
compatibility.exit = function(code)
|
||||
c = code
|
||||
end
|
||||
local idle = uv.new_idle()
|
||||
idle:start(function()
|
||||
idle:stop()
|
||||
coroutine.resume(co)
|
||||
end)
|
||||
uv.run()
|
||||
os.exit(c)
|
||||
end
|
||||
@ -0,0 +1,84 @@
|
||||
local loop = require('promise').loop
|
||||
|
||||
local function testAsynchronousFunction(name)
|
||||
it(name .. 'is asynchronous', function()
|
||||
local called = spy.new(function() end)
|
||||
loop[name](function()
|
||||
called()
|
||||
done()
|
||||
end, 0)
|
||||
assert.spy(called).was_not_called()
|
||||
assert.True(wait())
|
||||
assert.spy(called).was_called()
|
||||
end)
|
||||
end
|
||||
|
||||
describe('EventLoop for Promise.', function()
|
||||
testAsynchronousFunction('setTimeout')
|
||||
testAsynchronousFunction('nextTick')
|
||||
testAsynchronousFunction('nextIdle')
|
||||
|
||||
describe('fire `nextIdle` is later than `nextTick`,', function()
|
||||
it('call `nextTick` first', function()
|
||||
local queue = {}
|
||||
local tick = {'tick'}
|
||||
local idle = {'idle'}
|
||||
loop.nextTick(function()
|
||||
table.insert(queue, tick)
|
||||
end)
|
||||
loop.nextIdle(function()
|
||||
table.insert(queue, idle)
|
||||
end)
|
||||
loop.setTimeout(done, 50)
|
||||
assert.True(wait())
|
||||
assert.same(tick, queue[1])
|
||||
assert.same(idle, queue[2])
|
||||
end)
|
||||
|
||||
it('call `nextIdle` first', function()
|
||||
local queue = {}
|
||||
local tick = {'tick'}
|
||||
local idle = {'idle'}
|
||||
loop.nextIdle(function()
|
||||
table.insert(queue, idle)
|
||||
end)
|
||||
loop.nextTick(function()
|
||||
table.insert(queue, tick)
|
||||
end)
|
||||
loop.setTimeout(done, 50)
|
||||
assert.True(wait())
|
||||
assert.same(tick, queue[1])
|
||||
assert.same(idle, queue[2])
|
||||
end)
|
||||
end)
|
||||
|
||||
it('call `nextTick` in `nextTick` event', function()
|
||||
local onTick = spy.new(function() end)
|
||||
local onNextTick = spy.new(function() end)
|
||||
loop.nextTick(function()
|
||||
onTick()
|
||||
loop.nextTick(function()
|
||||
onNextTick()
|
||||
assert.spy(onNextTick).was_called()
|
||||
done()
|
||||
end)
|
||||
assert.spy(onTick).was_called()
|
||||
assert.spy(onNextTick).was_not_called()
|
||||
end)
|
||||
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('override callWrapper method', function()
|
||||
local rawCallWrapper = loop.callWrapper
|
||||
local callback = function() end
|
||||
loop.callWrapper = function(fn)
|
||||
loop.callWrapper = rawCallWrapper
|
||||
assert.same(callback, fn)
|
||||
done()
|
||||
end
|
||||
loop.nextTick(callback)
|
||||
assert.True(wait())
|
||||
loop.callWrapper = rawCallWrapper
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,70 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local setTimeout = helpers.setTimeout
|
||||
local testFulfilled = helpers.testFulfilled
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
describe('2.1.2.1: When fulfilled, a promise: must not transition to any other state.', function()
|
||||
local onFulfilled, onRejected = spy.new(function() end), spy.new(function() end)
|
||||
|
||||
before_each(function()
|
||||
onFulfilled:clear()
|
||||
onRejected:clear()
|
||||
end)
|
||||
|
||||
testFulfilled(it, assert, dummy, function(p)
|
||||
local onFulfilledCalled = false
|
||||
p:thenCall(function()
|
||||
onFulfilledCalled = true
|
||||
end, function()
|
||||
assert.False(onFulfilledCalled)
|
||||
done()
|
||||
end)
|
||||
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 50)
|
||||
end)
|
||||
|
||||
it('trying to fulfill then immediately reject', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
resolve(dummy)
|
||||
reject(dummy)
|
||||
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called()
|
||||
assert.spy(onRejected).was_not_called()
|
||||
end)
|
||||
|
||||
it('trying to fulfill then reject, delayed', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
resolve(dummy)
|
||||
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called()
|
||||
assert.spy(onRejected).was_not_called()
|
||||
end)
|
||||
|
||||
it('trying to fulfill immediately then reject, delayed', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
reject(dummy)
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called()
|
||||
assert.spy(onRejected).was_not_called()
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,70 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testRejected = helpers.testRejected
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local setTimeout = helpers.setTimeout
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
describe('2.1.3.1: When rejected, a promise: must not transition to any other state.', function()
|
||||
local onFulfilled, onRejected = spy.new(function() end), spy.new(function() end)
|
||||
|
||||
before_each(function()
|
||||
onFulfilled:clear()
|
||||
onRejected:clear()
|
||||
end)
|
||||
|
||||
testRejected(it, assert, dummy, function(p)
|
||||
local onRejectedCalled = false
|
||||
p:thenCall(function()
|
||||
assert.False(onRejectedCalled)
|
||||
done()
|
||||
end, function()
|
||||
onRejectedCalled = true
|
||||
end)
|
||||
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 50)
|
||||
end)
|
||||
|
||||
it('trying to reject then immediately fulfill', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
reject(dummy)
|
||||
resolve(dummy)
|
||||
|
||||
setTimeout(function()
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
assert.spy(onRejected).was_called()
|
||||
end)
|
||||
|
||||
it('trying to reject then fulfill, delayed', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
reject(dummy)
|
||||
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
assert.spy(onRejected).was_called()
|
||||
end)
|
||||
|
||||
it('trying to reject immediately then fulfill, delayed', function()
|
||||
local p, resolve, reject = deferredPromise()
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
resolve(dummy)
|
||||
done()
|
||||
end, 50)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
assert.spy(onRejected).was_called()
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,78 @@
|
||||
local promise = require('promise')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
describe('2.2.1: Both `onFulfilled` and `onRejected` are optional arguments.', function()
|
||||
describe('2.2.1.1: If `onFulfilled` is not a function, it must be ignored.', function()
|
||||
describe('applied to a directly-rejected promise', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
it('`onFulfilled` is ' .. stringRepresentation, function()
|
||||
promise.reject(dummy)
|
||||
:thenCall(nonFunction, function()
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction({}, '`a table`')
|
||||
end)
|
||||
|
||||
describe('applied to a promise rejected and then chained off of', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
it('`onFulfilled` is ' .. stringRepresentation, function()
|
||||
promise.reject(dummy)
|
||||
:thenCall(function() end, nil)
|
||||
:thenCall(nonFunction, function()
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction({}, '`a table`')
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.1.2: If `onRejected` is not a function, it must be ignored.', function()
|
||||
describe('applied to a directly-fulfilled promise', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
it('`onRejected` is ' .. stringRepresentation, function()
|
||||
promise.resolve(dummy)
|
||||
:thenCall(function()
|
||||
done()
|
||||
end, nonFunction)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction({}, '`a table`')
|
||||
end)
|
||||
|
||||
describe('applied to a promise fulfilled and then chained off of', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
it('`onRejected` is ' .. stringRepresentation, function()
|
||||
promise.resolve(dummy)
|
||||
:thenCall(nil, function() end)
|
||||
:thenCall(function()
|
||||
done()
|
||||
end, nonFunction)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction({}, '`a table`')
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,122 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testFulfilled = helpers.testFulfilled
|
||||
local setTimeout = helpers.setTimeout
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local promise = require('promise')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
|
||||
describe('2.2.2: If `onFulfilled` is a function,', function()
|
||||
describe('2.2.2.1: it must be called after `promise` is fulfilled, ' ..
|
||||
'with `promise`’s fulfillment value as its first argument.', function()
|
||||
testFulfilled(it, assert, sentinel, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.2.2: it must not be called before `promise` is fulfilled', function()
|
||||
it('fulfilled after a delay', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled)
|
||||
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
end)
|
||||
|
||||
it('never fulfilled', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
local p = deferredPromise()
|
||||
p:thenCall(onFulfilled)
|
||||
assert.False(wait(30))
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.2.3: it must not be called more than once.', function()
|
||||
it('already-fulfilled', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
promise.resolve(dummy):thenCall(onFulfilled)
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to fulfill a pending promise more than once, immediately', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled)
|
||||
resolve(dummy)
|
||||
resolve(dummy)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to fulfill a pending promise more than once, delayed', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled)
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
resolve(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to fulfill a pending promise more than once, immediately then delayed', function()
|
||||
local onFulfilled = spy.new(done)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled)
|
||||
resolve(dummy)
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
end)
|
||||
|
||||
it('when multiple `thenCall` calls are made, spaced apart in time', function()
|
||||
local onFulfilled1 = spy.new(function() end)
|
||||
local onFulfilled2 = spy.new(function() end)
|
||||
local onFulfilled3 = spy.new(function() end)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled1)
|
||||
setTimeout(function()
|
||||
p:thenCall(onFulfilled2)
|
||||
end, 10)
|
||||
setTimeout(function()
|
||||
p:thenCall(onFulfilled3)
|
||||
end, 20)
|
||||
setTimeout(function()
|
||||
resolve(dummy)
|
||||
done()
|
||||
end, 30)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled1).was_called(1)
|
||||
assert.spy(onFulfilled2).was_called(1)
|
||||
assert.spy(onFulfilled3).was_called(1)
|
||||
end)
|
||||
|
||||
it('when `thenCall` is interleaved with fulfillment', function()
|
||||
local onFulfilled1 = spy.new(function() end)
|
||||
local onFulfilled2 = spy.new(function() end)
|
||||
local p, resolve = deferredPromise()
|
||||
p:thenCall(onFulfilled1)
|
||||
resolve(dummy)
|
||||
setTimeout(function()
|
||||
p:thenCall(onFulfilled2)
|
||||
done()
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onFulfilled1).was_called(1)
|
||||
assert.spy(onFulfilled2).was_called(1)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,122 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testRejected = helpers.testRejected
|
||||
local setTimeout = helpers.setTimeout
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local promise = require('promise')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
|
||||
describe('2.2.3: If `onRejected` is a function,', function()
|
||||
describe('2.2.3.1: it must be called after `promise` is rejected, ' ..
|
||||
'with `promise`’s rejection reason as its first argument.', function()
|
||||
testRejected(it, assert, sentinel, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.3.2: it must not be called before `promise` is rejected', function()
|
||||
it('rejected after a delay', function()
|
||||
local onRejected = spy.new(done)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected)
|
||||
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called(1)
|
||||
end)
|
||||
|
||||
it('never rejected', function()
|
||||
local onRejected = spy.new(done)
|
||||
local p = deferredPromise()
|
||||
p:thenCall(nil, onRejected)
|
||||
assert.False(wait(30))
|
||||
assert.spy(onRejected).was_not_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.3.3: it must not be called more than once.', function()
|
||||
it('already-rejected', function()
|
||||
local onRejected = spy.new(done)
|
||||
promise.reject(dummy):thenCall(nil, onRejected)
|
||||
assert.spy(onRejected).was_not_called()
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to reject a pending promise more than once, immediately', function()
|
||||
local onRejected = spy.new(done)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected)
|
||||
reject(dummy)
|
||||
reject(dummy)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to reject a pending promise more than once, delayed', function()
|
||||
local onRejected = spy.new(done)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected)
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
reject(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called(1)
|
||||
end)
|
||||
|
||||
it('trying to reject a pending promise more than once, immediately then delayed', function()
|
||||
local onRejected = spy.new(done)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected)
|
||||
reject(dummy)
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called(1)
|
||||
end)
|
||||
|
||||
it('when multiple `thenCall` calls are made, spaced apart in time', function()
|
||||
local onRejected1 = spy.new(function() end)
|
||||
local onRejected2 = spy.new(function() end)
|
||||
local onRejected3 = spy.new(function() end)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected1)
|
||||
setTimeout(function()
|
||||
p:thenCall(nil, onRejected2)
|
||||
end, 15)
|
||||
setTimeout(function()
|
||||
p:thenCall(nil, onRejected3)
|
||||
end, 25)
|
||||
setTimeout(function()
|
||||
reject(dummy)
|
||||
done()
|
||||
end, 35)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected1).was_called(1)
|
||||
assert.spy(onRejected2).was_called(1)
|
||||
assert.spy(onRejected3).was_called(1)
|
||||
end)
|
||||
|
||||
it('when `thenCall` is interleaved with rejection', function()
|
||||
local onRejected1 = spy.new(function() end)
|
||||
local onRejected2 = spy.new(function() end)
|
||||
local p, _, reject = deferredPromise()
|
||||
p:thenCall(nil, onRejected1)
|
||||
reject(dummy)
|
||||
setTimeout(function()
|
||||
p:thenCall(nil, onRejected2)
|
||||
done()
|
||||
end, 10)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected1).was_called(1)
|
||||
assert.spy(onRejected2).was_called(1)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,128 @@
|
||||
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)
|
||||
@ -0,0 +1,295 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testFulfilled = helpers.testFulfilled
|
||||
local testRejected = helpers.testRejected
|
||||
local setTimeout = helpers.setTimeout
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
local sentinel2 = {sentinel = 'sentinel2'}
|
||||
local sentinel3 = {sentinel = 'sentinel3'}
|
||||
|
||||
describe('2.2.6: `thenCall` may be called multiple times on the same promise.', function()
|
||||
local function callbackAggregator(times, ultimateCallback)
|
||||
local soFar = 0
|
||||
return function()
|
||||
soFar = soFar + 1
|
||||
if soFar == times then
|
||||
ultimateCallback()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe('2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks ' ..
|
||||
'must execute in the order of their originating calls to `thenCall`.', function()
|
||||
describe('multiple boring fulfillment handlers', function()
|
||||
testFulfilled(it, assert, sentinel, function(p)
|
||||
local onFulfilled1 = spy.new(function() end)
|
||||
local onFulfilled2 = spy.new(function() end)
|
||||
local onFulfilled3 = spy.new(function() end)
|
||||
local onRejected = spy.new(function() end)
|
||||
p:thenCall(onFulfilled1, onRejected)
|
||||
p:thenCall(onFulfilled2, onRejected)
|
||||
p:thenCall(onFulfilled3, onRejected)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
|
||||
assert.spy(onFulfilled1).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled2).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled3).was_called_with(sentinel)
|
||||
assert.spy(onRejected).was_not_called()
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('multiple fulfillment handlers, one of which throws', function()
|
||||
testFulfilled(it, assert, sentinel, function(p)
|
||||
local onFulfilled1 = spy.new(function() end)
|
||||
local onFulfilled2 = spy.new(function()
|
||||
error()
|
||||
end)
|
||||
local onFulfilled3 = spy.new(function() end)
|
||||
local onRejected = spy.new(function() end)
|
||||
p:thenCall(onFulfilled1, onRejected)
|
||||
p:thenCall(onFulfilled2, onRejected):catch(function() end)
|
||||
p:thenCall(onFulfilled3, onRejected)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
assert.spy(onFulfilled1).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled2).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled3).was_called_with(sentinel)
|
||||
assert.spy(onRejected).was_not_called()
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('results in multiple branching chains with their own fulfillment values', function()
|
||||
testFulfilled(it, assert, dummy, function(p)
|
||||
local semiDone = callbackAggregator(3, function()
|
||||
done()
|
||||
end)
|
||||
|
||||
p:thenCall(function()
|
||||
return sentinel
|
||||
end):thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
semiDone()
|
||||
end)
|
||||
|
||||
p:thenCall(function()
|
||||
error(sentinel2)
|
||||
end):thenCall(nil, function(reason)
|
||||
assert.equal(sentinel2, reason)
|
||||
semiDone()
|
||||
end)
|
||||
|
||||
p:thenCall(function()
|
||||
return sentinel3
|
||||
end):thenCall(function(value)
|
||||
assert.equal(sentinel3, value)
|
||||
semiDone()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`onFulfilled` handlers are called in the original order', function()
|
||||
local queue = {}
|
||||
local function enQueue(value)
|
||||
table.insert(queue, value)
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
queue = {}
|
||||
end)
|
||||
|
||||
testFulfilled(it, assert, dummy, function(p)
|
||||
local function onFulfilled1()
|
||||
enQueue(1)
|
||||
end
|
||||
|
||||
local function onFulfilled2()
|
||||
enQueue(2)
|
||||
end
|
||||
|
||||
local function onFulfilled3()
|
||||
enQueue(3)
|
||||
end
|
||||
|
||||
p:thenCall(onFulfilled1)
|
||||
p:thenCall(onFulfilled2)
|
||||
p:thenCall(onFulfilled3)
|
||||
|
||||
p:thenCall(function()
|
||||
assert.same({1, 2, 3}, queue)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('even when one handler is added inside another handler', function()
|
||||
testFulfilled(it, assert, dummy, function(p)
|
||||
local function onFulfilled1()
|
||||
enQueue(1)
|
||||
end
|
||||
|
||||
local function onFulfilled2()
|
||||
enQueue(2)
|
||||
end
|
||||
|
||||
local function onFulfilled3()
|
||||
enQueue(3)
|
||||
end
|
||||
|
||||
p:thenCall(function()
|
||||
onFulfilled1()
|
||||
p:thenCall(onFulfilled3)
|
||||
end)
|
||||
p:thenCall(onFulfilled2)
|
||||
|
||||
p:thenCall(function()
|
||||
setTimeout(function()
|
||||
assert.same({1, 2, 3}, queue)
|
||||
done()
|
||||
end, 10)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks ' ..
|
||||
'must execute in the order of their originating calls to `thenCall`.', function()
|
||||
describe('multiple boring rejection handlers', function()
|
||||
testRejected(it, assert, sentinel, function(p)
|
||||
local onFulfilled = spy.new(function() end)
|
||||
local onRejected1 = spy.new(function() end)
|
||||
local onRejected2 = spy.new(function() end)
|
||||
local onRejected3 = spy.new(function() end)
|
||||
p:thenCall(onFulfilled, onRejected1)
|
||||
p:thenCall(onFulfilled, onRejected2)
|
||||
p:thenCall(onFulfilled, onRejected3)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
|
||||
assert.spy(onRejected1).was_called_with(sentinel)
|
||||
assert.spy(onRejected2).was_called_with(sentinel)
|
||||
assert.spy(onRejected3).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('multiple rejection handlers, one of which throws', function()
|
||||
testRejected(it, assert, sentinel, function(p)
|
||||
local onFulfilled = spy.new(function() end)
|
||||
local onRejected1 = spy.new(function() end)
|
||||
local onRejected2 = spy.new(function()
|
||||
error()
|
||||
end)
|
||||
local onRejected3 = spy.new(function() end)
|
||||
p:thenCall(onFulfilled, onRejected1)
|
||||
p:thenCall(onFulfilled, onRejected2):catch(function() end)
|
||||
p:thenCall(onFulfilled, onRejected3)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
|
||||
assert.spy(onRejected1).was_called_with(sentinel)
|
||||
assert.spy(onRejected2).was_called_with(sentinel)
|
||||
assert.spy(onRejected3).was_called_with(sentinel)
|
||||
assert.spy(onFulfilled).was_not_called()
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('results in multiple branching chains with their own rejection values', function()
|
||||
testRejected(it, assert, dummy, function(p)
|
||||
local semiDone = callbackAggregator(3, function()
|
||||
done()
|
||||
end)
|
||||
|
||||
p:thenCall(nil, function()
|
||||
return sentinel
|
||||
end):thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
semiDone()
|
||||
end)
|
||||
|
||||
p:thenCall(nil, function()
|
||||
error(sentinel2)
|
||||
end):thenCall(nil, function(reason)
|
||||
assert.equal(sentinel2, reason)
|
||||
semiDone()
|
||||
end)
|
||||
|
||||
p:thenCall(nil, function()
|
||||
return sentinel3
|
||||
end):thenCall(function(value)
|
||||
assert.equal(sentinel3, value)
|
||||
semiDone()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`onRejected` handlers are called in the original order', function()
|
||||
local queue = {}
|
||||
local function enQueue(value)
|
||||
table.insert(queue, value)
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
queue = {}
|
||||
end)
|
||||
|
||||
testRejected(it, assert, dummy, function(p)
|
||||
local function onRejected1()
|
||||
enQueue(1)
|
||||
end
|
||||
|
||||
local function onRejected2()
|
||||
enQueue(2)
|
||||
end
|
||||
|
||||
local function onRejected3()
|
||||
enQueue(3)
|
||||
end
|
||||
|
||||
p:thenCall(nil, onRejected1)
|
||||
p:thenCall(nil, onRejected2)
|
||||
p:thenCall(nil, onRejected3)
|
||||
p:thenCall(nil, function()
|
||||
assert.same({1, 2, 3}, queue)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('even when one handler is added inside another handler', function()
|
||||
testRejected(it, assert, dummy, function(p)
|
||||
local function onRejected1()
|
||||
enQueue(1)
|
||||
end
|
||||
|
||||
local function onRejected2()
|
||||
enQueue(2)
|
||||
end
|
||||
|
||||
local function onRejected3()
|
||||
enQueue(3)
|
||||
end
|
||||
|
||||
p:thenCall(nil, function()
|
||||
onRejected1()
|
||||
p:thenCall(nil, onRejected3)
|
||||
end)
|
||||
p:thenCall(nil, onRejected2)
|
||||
p:thenCall(nil, function()
|
||||
setTimeout(function()
|
||||
assert.same({1, 2, 3}, queue)
|
||||
done()
|
||||
end, 15)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,98 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testFulfilled = helpers.testFulfilled
|
||||
local testRejected = helpers.testRejected
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
local other = {other = 'other'}
|
||||
local reasons = require('spec.helpers.reasons')
|
||||
|
||||
describe('2.2.7: `thenCall` must return a promise: ' ..
|
||||
'`promise2 = promise1.thenCall(onFulfilled, onRejected)', function()
|
||||
it('is a promise', function()
|
||||
local p1 = deferredPromise()
|
||||
local p2 = p1:thenCall()
|
||||
|
||||
assert.True(type(p2) == 'table' or type(p2) == 'function')
|
||||
assert.is.not_equal(p2, nil)
|
||||
assert.equal('function', type(p2.thenCall))
|
||||
end)
|
||||
|
||||
describe('2.2.7.1: If either `onFulfilled` or `onRejected` returns a value `x`, ' ..
|
||||
'run the Promise Resolution Procedure `[[Resolve]](promise2, x)`', function()
|
||||
it('see separate 3.3 tests', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, ' ..
|
||||
'`promise2` must be rejected with `e` as the reason.', function()
|
||||
local function testReason(expectedReason, stringRepresentation)
|
||||
describe('The reason is ' .. stringRepresentation, function()
|
||||
testFulfilled(it, assert, dummy, function(p1)
|
||||
local p2 = p1:thenCall(function()
|
||||
error(expectedReason)
|
||||
end)
|
||||
p2:thenCall(nil, function(actualReason)
|
||||
assert.equal(expectedReason, actualReason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
testRejected(it, assert, dummy, function(p1)
|
||||
local p2 = p1:thenCall(nil, function()
|
||||
error(expectedReason)
|
||||
end)
|
||||
p2:thenCall(nil, function(actualReason)
|
||||
assert.equal(expectedReason, actualReason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
for reasonStr, reason in pairs(reasons) do
|
||||
testReason(reason(), reasonStr)
|
||||
end
|
||||
end)
|
||||
|
||||
describe('2.2.7.3: If `onFulfilled` is not a function and `promise1` is fulfilled, ' ..
|
||||
'`promise2` must be fulfilled with the same value', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
describe('`onFulfilled` is' .. stringRepresentation, function()
|
||||
testFulfilled(it, assert, sentinel, function(p1)
|
||||
local p2 = p1:thenCall(nonFunction)
|
||||
p2:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction(setmetatable({}, {}), 'a metatable')
|
||||
testNonFunction({function() return other end}, 'an table containing a function')
|
||||
end)
|
||||
|
||||
describe('2.2.7.4: If `onRejected` is not a function and `promise1` is rejected, ' ..
|
||||
'`promise2` must be rejected with the same reason', function()
|
||||
local function testNonFunction(nonFunction, stringRepresentation)
|
||||
describe('`onRejected` is' .. stringRepresentation, function()
|
||||
testRejected(it, assert, sentinel, function(p1)
|
||||
local p2 = p1:thenCall(nonFunction)
|
||||
p2:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
testNonFunction(nil, '`nil`')
|
||||
testNonFunction(false, '`false`')
|
||||
testNonFunction(5, '`5`')
|
||||
testNonFunction(setmetatable({}, {}), 'a metatable')
|
||||
testNonFunction({function() return other end}, 'an table containing a function')
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,31 @@
|
||||
local promise = require('promise')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
describe('2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a ' ..
|
||||
'`TypeError` as the reason.', function()
|
||||
it('via return from a fulfilled promise', function()
|
||||
local p
|
||||
p = promise.resolve(dummy):thenCall(function()
|
||||
return p
|
||||
end)
|
||||
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.truthy(reason:match('^TypeError'))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('via return from a rejected promise', function()
|
||||
local p
|
||||
p = promise.reject(dummy):thenCall(nil, function()
|
||||
return p
|
||||
end)
|
||||
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.truthy(reason:match('^TypeError'))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,97 @@
|
||||
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)
|
||||
@ -0,0 +1,800 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local deferredPromise = helpers.deferredPromise
|
||||
local promise = require('promise')
|
||||
local setTimeout = helpers.setTimeout
|
||||
local reasons = require('spec.helpers.reasons')
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
local other = {other = 'other'}
|
||||
local thenables = require('spec.helpers.thenables')
|
||||
|
||||
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.3: Otherwise, if `x` is a table or function,', function()
|
||||
describe('2.3.3.1: Let `thenCall` be `x.thenCall`', function()
|
||||
describe('`x` is a table', function()
|
||||
local thenCallRetrieved = spy.new(function() end)
|
||||
|
||||
before_each(function()
|
||||
thenCallRetrieved:clear()
|
||||
end)
|
||||
|
||||
testPromiseResolution(function()
|
||||
local x = {}
|
||||
setmetatable(x, {
|
||||
__index = function(_, k)
|
||||
if k == 'thenCall' then
|
||||
thenCallRetrieved()
|
||||
return function(_, resolvePromise)
|
||||
resolvePromise()
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
return x
|
||||
end, function(p)
|
||||
p:thenCall(function()
|
||||
assert.spy(thenCallRetrieved).was_called(1)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.2: If retrieving the property `x.thenCall` results in a thrown exception ' ..
|
||||
'`e`, reject `promise` with `e` as the reason.', function()
|
||||
local function testRejectionViaThrowingGetter(e, stringRepresentation)
|
||||
describe('`e` is ' .. stringRepresentation, function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function()
|
||||
error(e)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(e, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
for reasonStr, reason in pairs(reasons) do
|
||||
testRejectionViaThrowingGetter(reason(), reasonStr)
|
||||
end
|
||||
end)
|
||||
|
||||
describe('2.3.3.3: If `thenCall` is a function, call it with `x` as `self`, first ' ..
|
||||
'argument `resolvePromise`, and second argument `rejectPromise`', function()
|
||||
testPromiseResolution(function()
|
||||
local x
|
||||
x = {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
assert.equal(x, self)
|
||||
assert.True(type(resolvePromise) == 'function')
|
||||
assert.True(type(rejectPromise) == 'function')
|
||||
resolvePromise()
|
||||
end
|
||||
}
|
||||
return x
|
||||
end, function(p)
|
||||
p:thenCall(function()
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.3.1: If/when `resolvePromise` is called with value `y`, ' ..
|
||||
'run `[[Resolve]](promise, y)`', function()
|
||||
local function testCallingResolvePromise(yFactory, stringRepresentation, test)
|
||||
describe('`y` is ' .. stringRepresentation, function()
|
||||
describe('`thenCall` calls `resolvePromise` synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(yFactory())
|
||||
end
|
||||
}
|
||||
end, test)
|
||||
end)
|
||||
|
||||
describe('`thenCall` calls `resolvePromise` asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
setTimeout(function()
|
||||
resolvePromise(yFactory())
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, test)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local function testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation,
|
||||
fulfillmentValue)
|
||||
testCallingResolvePromise(yFactory, stringRepresentation, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(fulfillmentValue, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local function testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation,
|
||||
rejectionReason)
|
||||
testCallingResolvePromise(yFactory, stringRepresentation, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(rejectionReason, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
describe('`y` is not a thenable', function()
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return nil
|
||||
end, '`null`', nil)
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return false
|
||||
end, '`false`', false)
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return 5
|
||||
end, '`5`', 5)
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return sentinel
|
||||
end, '`an table`', sentinel)
|
||||
end)
|
||||
|
||||
describe('`y` is a thenable', function()
|
||||
for stringRepresentation, factory in pairs(thenables.fulfilled) do
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return factory(sentinel)
|
||||
end, stringRepresentation, sentinel)
|
||||
end
|
||||
for stringRepresentation, factory in pairs(thenables.rejected) do
|
||||
testCallingResolvePromiseRejectsWith(function()
|
||||
return factory(sentinel)
|
||||
end, stringRepresentation, sentinel)
|
||||
end
|
||||
end)
|
||||
|
||||
describe('`y` is a thenable for a thenable', function()
|
||||
for outerString, outerFactory in pairs(thenables.fulfilled) do
|
||||
for innerString, factory in pairs(thenables.fulfilled) do
|
||||
local stringRepresentation = outerString .. ' for ' .. innerString
|
||||
testCallingResolvePromiseFulfillsWith(function()
|
||||
return outerFactory(factory(sentinel))
|
||||
end, stringRepresentation, sentinel)
|
||||
end
|
||||
for innerString, factory in pairs(thenables.rejected) do
|
||||
local stringRepresentation = outerString .. ' for ' .. innerString
|
||||
testCallingResolvePromiseRejectsWith(function()
|
||||
return outerFactory(factory(sentinel))
|
||||
end, stringRepresentation, sentinel)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.3.2: If/when `rejectPromise` is called with reason `r`, reject `promise` with `r`', function()
|
||||
local function testCallingRejectPromise(r, stringRepresentation, test)
|
||||
describe('`r` is ' .. stringRepresentation, function()
|
||||
describe('`thenCall` calls `rejectPromise` synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(r)
|
||||
end
|
||||
}
|
||||
end, test)
|
||||
end)
|
||||
|
||||
describe('`thenCall` calls `rejectPromise` asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
setTimeout(function()
|
||||
rejectPromise(r)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, test)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local function testCallingRejectPromiseRejectsWith(rejectionReason, stringRepresentation)
|
||||
testCallingRejectPromise(rejectionReason, stringRepresentation, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(rejectionReason, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
for reasonStr, reason in pairs(reasons) do
|
||||
testCallingRejectPromiseRejectsWith(reason(), reasonStr)
|
||||
end
|
||||
end)
|
||||
|
||||
describe('2.3.3.3.3: If both `resolvePromise` and `rejectPromise` are called, or multiple ' ..
|
||||
'calls to the same argument are made, the first call takes precedence, and any further ' ..
|
||||
'calls are ignored.', function()
|
||||
describe('calling `resolvePromise` then `rejectPromise`, both synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` synchronously then `rejectPromise` asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
setTimeout(function()
|
||||
rejectPromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` then `rejectPromise`, both asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
setTimeout(function()
|
||||
resolvePromise(sentinel)
|
||||
end, 0)
|
||||
setTimeout(function()
|
||||
rejectPromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` with an asynchronously-fulfilled promise, then calling ' ..
|
||||
'`rejectPromise`, both synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
local p, resolve = deferredPromise()
|
||||
setTimeout(function()
|
||||
resolve(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` with an asynchronously-rejected promise, then calling ' ..
|
||||
'`rejectPromise`, both synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
local p, _, reject = deferredPromise()
|
||||
setTimeout(function()
|
||||
reject(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `rejectPromise` then `resolvePromise`, both synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
local p, resolve = deferredPromise()
|
||||
setTimeout(function()
|
||||
resolve(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `rejectPromise` synchronously then `resolvePromise` asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(sentinel)
|
||||
setTimeout(function()
|
||||
resolvePromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `rejectPromise` then `resolvePromise`, both asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
setTimeout(function()
|
||||
rejectPromise(sentinel)
|
||||
end, 0)
|
||||
setTimeout(function()
|
||||
resolvePromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` twice synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` twice, first synchronously then asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
setTimeout(function()
|
||||
resolvePromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` twice, both times asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
setTimeout(function()
|
||||
resolvePromise(sentinel)
|
||||
end, 0)
|
||||
setTimeout(function()
|
||||
resolvePromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` with an asynchronously-fulfilled promise, ' ..
|
||||
'then calling it again, both times synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
local p, resolve = deferredPromise()
|
||||
setTimeout(function()
|
||||
resolve(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` with an asynchronously-rejected promise, ' ..
|
||||
'then calling it again, both times synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
local p, _, reject = deferredPromise()
|
||||
setTimeout(function()
|
||||
reject(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `rejectPromise` twice synchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(sentinel)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `resolvePromise` twice, first synchronously then asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(sentinel)
|
||||
setTimeout(function()
|
||||
rejectPromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('calling `rejectPromise` twice, both times asynchronously', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
setTimeout(function()
|
||||
rejectPromise(sentinel)
|
||||
end, 0)
|
||||
setTimeout(function()
|
||||
rejectPromise(other)
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('saving and abusing `resolvePromise` and `rejectPromise`', function()
|
||||
local savedResolvePromise, savedRejectPromise
|
||||
|
||||
before_each(function()
|
||||
savedResolvePromise, savedRejectPromise = nil, nil
|
||||
end)
|
||||
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
savedResolvePromise, savedRejectPromise = resolvePromise, rejectPromise
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
local onFulfilled, onRejected = spy.new(function() end), spy.new(function() end)
|
||||
p:thenCall(onFulfilled, onRejected)
|
||||
if savedResolvePromise and savedRejectPromise then
|
||||
savedResolvePromise(dummy)
|
||||
savedResolvePromise(dummy)
|
||||
savedRejectPromise(dummy)
|
||||
savedRejectPromise(dummy)
|
||||
end
|
||||
|
||||
setTimeout(function()
|
||||
savedResolvePromise(dummy)
|
||||
savedResolvePromise(dummy)
|
||||
savedRejectPromise(dummy)
|
||||
savedRejectPromise(dummy)
|
||||
end, 10)
|
||||
|
||||
setTimeout(function()
|
||||
assert.spy(onFulfilled).was_called(1)
|
||||
assert.spy(onRejected).was_not_called()
|
||||
done()
|
||||
end, 50)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.3.4: If calling `thenCall` throws an exception `e`,', function()
|
||||
describe('2.3.3.3.4.1: If `resolvePromise` or `rejectPromise` have been called, ignore it.', function()
|
||||
describe('`resolvePromise` was called with a non-thenable', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`resolvePromise` was called with an asynchronously-fulfilled promise', function()
|
||||
testPromiseResolution(function()
|
||||
local p, resolve = deferredPromise()
|
||||
setTimeout(function()
|
||||
resolve(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`resolvePromise` was called with an asynchronously-rejected promise', function()
|
||||
testPromiseResolution(function()
|
||||
local p, _, reject = deferredPromise()
|
||||
setTimeout(function()
|
||||
reject(sentinel)
|
||||
end, 10)
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(p)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`rejectPromise` was called', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
rejectPromise(sentinel)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`resolvePromise` then `rejectPromise` were called', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
resolvePromise(sentinel)
|
||||
rejectPromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`rejectPromise` then `resolvePromise` were called', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _ = self
|
||||
rejectPromise(sentinel)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.3.4.2: Otherwise, reject `promise` with `e` as the reason.', function()
|
||||
describe('straightforward case', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function()
|
||||
error(sentinel)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`resolvePromise` is called asynchronously before the `throw`', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
setTimeout(function()
|
||||
resolvePromise(other)
|
||||
end, 0)
|
||||
error(sentinel)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('`rejectPromise` is called asynchronously before the `throw`', function()
|
||||
testPromiseResolution(function()
|
||||
return {
|
||||
thenCall = function(self, resolvePromise, rejectPromise)
|
||||
local _, _ = self, resolvePromise
|
||||
setTimeout(function()
|
||||
rejectPromise(other)
|
||||
end, 0)
|
||||
error(sentinel)
|
||||
end
|
||||
}
|
||||
end, function(p)
|
||||
p:thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('2.3.3.4: If `thenCall` is not a function, fulfill promise with `x`', function()
|
||||
local function testFulfillViaNonFunction(thenCall, stringRepresentation)
|
||||
local x = nil
|
||||
|
||||
before_each(function()
|
||||
x = {thenCall = thenCall}
|
||||
end)
|
||||
|
||||
describe('thenCall is ' .. stringRepresentation, function()
|
||||
testPromiseResolution(function()
|
||||
return x
|
||||
end, function(p)
|
||||
p:thenCall(function(value)
|
||||
assert.equal(x, value)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
testFulfillViaNonFunction(5, '`5`')
|
||||
testFulfillViaNonFunction({}, 'a table')
|
||||
testFulfillViaNonFunction({function() end}, 'a table containing a function')
|
||||
testFulfillViaNonFunction(setmetatable({}, {}), 'a metatable')
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,35 @@
|
||||
local helpers = require('spec.helpers.init')
|
||||
local testFulfilled = helpers.testFulfilled
|
||||
local testRejected = helpers.testRejected
|
||||
local dummy = {dummy = 'dummy'}
|
||||
|
||||
describe('2.3.4: If `x` is not an object or function, fulfill `promise` with `x`', function()
|
||||
local function testValue(expectedValue, stringRepresentation)
|
||||
describe('The value is ' .. stringRepresentation, function()
|
||||
testFulfilled(it, assert, dummy, function(p1)
|
||||
local p2 = p1:thenCall(function()
|
||||
return expectedValue
|
||||
end)
|
||||
p2:thenCall(function(actualValue)
|
||||
assert.equal(expectedValue, actualValue)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
|
||||
testRejected(it, assert, dummy, function(p1)
|
||||
local p2 = p1:thenCall(nil, function()
|
||||
return expectedValue
|
||||
end)
|
||||
p2:thenCall(function(actualValue)
|
||||
assert.equal(expectedValue, actualValue)
|
||||
done()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
testValue(nil, '`nil`')
|
||||
testValue(false, '`false`')
|
||||
testValue(true, '`true`')
|
||||
testValue(0, '`0`')
|
||||
end)
|
||||
@ -0,0 +1,418 @@
|
||||
local promise = require('promise')
|
||||
local helpers = require('spec.helpers.init')
|
||||
local basics = require('spec.helpers.basics')
|
||||
local reasons = require('spec.helpers.reasons')
|
||||
local setTimeout = helpers.setTimeout
|
||||
local dummy = {dummy = 'dummy'}
|
||||
local sentinel = {sentinel = 'sentinel'}
|
||||
local sentinel2 = {sentinel = 'sentinel2'}
|
||||
local sentinel3 = {sentinel = 'sentinel3'}
|
||||
local other = {other = 'other'}
|
||||
|
||||
describe('Extend Promise A+.', function()
|
||||
describe('Promise.resolve', function()
|
||||
describe('Resolving basic values.', function()
|
||||
local function testBasicResolve(expectedValue, stringRepresentation)
|
||||
it('The value is ' .. stringRepresentation ..
|
||||
', and the state of Promise become fulfilled at once.', function()
|
||||
local p = promise.resolve(expectedValue)
|
||||
assert.truthy(tostring(p):match('<fulfilled>'))
|
||||
p:thenCall(function(value)
|
||||
assert.equal(expectedValue, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
for valueStr, basicFn in pairs(basics) do
|
||||
testBasicResolve(basicFn(), valueStr)
|
||||
end
|
||||
end)
|
||||
|
||||
it('resolve another resolved Promise', function()
|
||||
local p1 = promise.resolve(dummy)
|
||||
local p2 = promise.resolve(p1)
|
||||
p2:thenCall(function(value)
|
||||
assert.equal(dummy, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
assert.equal(p1, p2)
|
||||
end)
|
||||
|
||||
it('resolve another rejected Promise', function()
|
||||
local p1 = promise.reject(dummy)
|
||||
local p2 = promise.resolve(p1)
|
||||
p2:thenCall(nil, function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
assert.equal(p1, p2)
|
||||
end)
|
||||
|
||||
it('resolve thenables and throwing Errors', function()
|
||||
local p1 = promise.resolve({
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(dummy)
|
||||
end
|
||||
})
|
||||
assert.True(promise.isInstance(p1))
|
||||
|
||||
local onFulfilled1 = spy.new(function(value)
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
p1:thenCall(onFulfilled1)
|
||||
|
||||
local thenable = {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
error(dummy)
|
||||
resolvePromise(other)
|
||||
end
|
||||
}
|
||||
local onRejected = spy.new(function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
local p2 = promise.resolve(thenable)
|
||||
p2:thenCall(nil, onRejected)
|
||||
|
||||
thenable = {
|
||||
thenCall = function(self, resolvePromise)
|
||||
local _ = self
|
||||
resolvePromise(dummy)
|
||||
error(other)
|
||||
end
|
||||
}
|
||||
local onFulfilled2 = spy.new(function(value)
|
||||
assert.equal(dummy, value)
|
||||
end)
|
||||
local p3 = promise.resolve(thenable)
|
||||
p3:thenCall(onFulfilled2)
|
||||
|
||||
assert.False(wait(30))
|
||||
assert.spy(onFulfilled1).was_called()
|
||||
assert.spy(onRejected).was_called()
|
||||
assert.spy(onFulfilled2).was_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.rejected.', function()
|
||||
describe('Rejecting reasons', function()
|
||||
local function testBasicReject(expectedReason, stringRepresentation)
|
||||
it('The reason is ' .. stringRepresentation ..
|
||||
', and the state of Promise become rejected at once.', function()
|
||||
local p = promise.reject(expectedReason)
|
||||
assert.truthy(tostring(p):match('<rejected>'))
|
||||
p:thenCall(nil, function(value)
|
||||
assert.equal(expectedReason, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end
|
||||
|
||||
for reasonStr, reason in pairs(reasons) do
|
||||
testBasicReject(reason(), reasonStr)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.catch method.', function()
|
||||
it('throw errors', function()
|
||||
local onRejected1 = spy.new(function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
end)
|
||||
promise(function()
|
||||
error(dummy)
|
||||
end):catch(onRejected1)
|
||||
|
||||
local onRejected2 = spy.new(function() end)
|
||||
promise(function(resolve)
|
||||
resolve()
|
||||
error(dummy)
|
||||
end):catch(onRejected2)
|
||||
|
||||
assert.False(wait(30))
|
||||
assert.spy(onRejected1).was_called()
|
||||
assert.spy(onRejected2).was_not_called()
|
||||
end)
|
||||
|
||||
it('is resolved', function()
|
||||
local onRejected1 = spy.new(function() end)
|
||||
local onFulfilled = spy.new(function() end)
|
||||
local onRejected2 = spy.new(function() end)
|
||||
promise.resolve(dummy)
|
||||
:catch(onRejected1)
|
||||
:thenCall(onFulfilled)
|
||||
:catch(onRejected2)
|
||||
|
||||
assert.False(wait(30))
|
||||
assert.spy(onRejected1).was_not_called()
|
||||
assert.spy(onFulfilled).was_called()
|
||||
assert.spy(onRejected2).was_not_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.finally method.', function()
|
||||
it('is pending', function()
|
||||
local onFinally = spy.new(done)
|
||||
promise(function() end):finally(onFinally)
|
||||
assert.False(wait(30))
|
||||
assert.spy(onFinally).was_not_called()
|
||||
end)
|
||||
|
||||
it('is fulfilled, next Promise is fulfilled with previous value', function()
|
||||
local onFinally = spy.new(function() end)
|
||||
local onFulfilled = spy.new(function(value)
|
||||
assert.equal(dummy, value)
|
||||
done()
|
||||
end)
|
||||
promise.resolve(dummy):finally(onFinally):thenCall(onFulfilled)
|
||||
assert.True(wait())
|
||||
assert.spy(onFinally).was_called()
|
||||
end)
|
||||
|
||||
it('is rejected, next Promise is rejected with previous reason', function()
|
||||
local onFinally = spy.new(function() end)
|
||||
local onRejected = spy.new(function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
done()
|
||||
end)
|
||||
promise.reject(dummy):finally(onFinally):thenCall(nil, onRejected)
|
||||
assert.True(wait())
|
||||
assert.spy(onFinally).was_called()
|
||||
end)
|
||||
|
||||
it('throw error on finally', function()
|
||||
local onRejected = spy.new(function(reason)
|
||||
assert.equal(dummy, reason)
|
||||
done()
|
||||
end)
|
||||
promise.resolve():finally(function()
|
||||
error(dummy)
|
||||
end):thenCall(nil, onRejected)
|
||||
assert.True(wait())
|
||||
assert.spy(onRejected).was_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.all method.', function()
|
||||
it('should be fulfilled immediately if element is empty', function()
|
||||
promise.all({}):thenCall(function(value)
|
||||
assert.same({}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
describe('wait for fulfillments,', function()
|
||||
it('use index table as elements', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
|
||||
promise.all({p1, p2, p3}):thenCall(function(value)
|
||||
assert.same({sentinel, sentinel2, sentinel3}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('use key-value table as elements, different from JavaScript', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
|
||||
promise.all({p1 = p1, p2 = p2, p3 = p3}):thenCall(function(value)
|
||||
assert.same({p1 = sentinel, p2 = sentinel2, p3 = sentinel3}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
it('is rejected if any of the elements are rejected', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(_, reject)
|
||||
setTimeout(function()
|
||||
reject(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
promise.all({p1, p2, p3}):thenCall(nil, function(reason)
|
||||
assert.equal(sentinel3, reason)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.allSettled method.', function()
|
||||
it('should be fulfilled immediately if element is empty', function()
|
||||
promise.allSettled({}):thenCall(function(value)
|
||||
assert.same({}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
describe('wait for fulfillments,', function()
|
||||
it('use index table as elements', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
|
||||
promise.allSettled({p1, p2, p3}):thenCall(function(value)
|
||||
assert.same({
|
||||
{status = 'fulfilled', value = sentinel},
|
||||
{status = 'fulfilled', value = sentinel2},
|
||||
{status = 'fulfilled', value = sentinel3}
|
||||
}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('use key-value table as elements, different from JavaScript', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
|
||||
promise.allSettled({p1 = p1, p2 = p2, p3 = p3}):thenCall(function(value)
|
||||
assert.same({
|
||||
p1 = {status = 'fulfilled', value = sentinel},
|
||||
p2 = {status = 'fulfilled', value = sentinel2},
|
||||
p3 = {status = 'fulfilled', value = sentinel3}
|
||||
}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
it('is always resolved even if any of the elements are rejected', function()
|
||||
local p1 = promise.resolve(sentinel)
|
||||
local p2 = sentinel2
|
||||
local p3 = promise(function(_, reject)
|
||||
setTimeout(function()
|
||||
reject(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
promise.allSettled({p1, p2, p3}):thenCall(function(value)
|
||||
assert.same({
|
||||
{status = 'fulfilled', value = sentinel},
|
||||
{status = 'fulfilled', value = sentinel2},
|
||||
{status = 'rejected', reason = sentinel3}
|
||||
}, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.any method.', function()
|
||||
it('should be rejected immediately if element is empty', function()
|
||||
promise.any({}):thenCall(nil, function(reason)
|
||||
assert.truthy(reason:match('^AggregateError'))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('resolve with the first promise to fulfill, even if a promise rejects first', function()
|
||||
local p1 = promise.reject(sentinel)
|
||||
local p2 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel2)
|
||||
end, 30)
|
||||
end)
|
||||
local p3 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel3)
|
||||
end, 10)
|
||||
end)
|
||||
promise.any({p1, p2, p3}):thenCall(function(value)
|
||||
assert.equal(sentinel3, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('reject with `AggregateError` if no promise fulfills', function()
|
||||
promise.any({promise.reject(dummy)}):thenCall(nil, function(reason)
|
||||
assert.not_equal(dummy, reason)
|
||||
assert.truthy(reason:match('^AggregateError'))
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('Promise.race method.', function()
|
||||
it('should be pending forever if element is empty', function()
|
||||
local onFinally = spy.new(done)
|
||||
promise.race({}):finally(onFinally)
|
||||
assert.spy(onFinally).was_not_called()
|
||||
assert.False(wait(30))
|
||||
assert.spy(onFinally).was_not_called()
|
||||
end)
|
||||
|
||||
describe('resolves or rejects with the first promise to settle,', function()
|
||||
it('resolve Promise is earlier than reject', function()
|
||||
local p1 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel)
|
||||
end, 10)
|
||||
end)
|
||||
local p2 = promise(function(_, reject)
|
||||
setTimeout(function()
|
||||
reject(sentinel2)
|
||||
end, 20)
|
||||
end)
|
||||
promise.race({p1, p2}):thenCall(function(value)
|
||||
assert.equal(sentinel, value)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
|
||||
it('reject Promise is earlier than resolve', function()
|
||||
local p1 = promise(function(_, reject)
|
||||
setTimeout(function()
|
||||
reject(sentinel)
|
||||
end, 10)
|
||||
end)
|
||||
local p2 = promise(function(resolve)
|
||||
setTimeout(function()
|
||||
resolve(sentinel2)
|
||||
end, 20)
|
||||
end)
|
||||
promise.race({p1, p2}):thenCall(nil, function(reason)
|
||||
assert.equal(sentinel, reason)
|
||||
done()
|
||||
end)
|
||||
assert.True(wait())
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
Reference in New Issue
Block a user