1

Regenerate nvim config

This commit is contained in:
2024-06-02 03:29:20 +02:00
parent 75eea0c030
commit ef2e28883d
5576 changed files with 604886 additions and 503 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)