local q={}local Z={to=true,be=true;been=true,have=true,was=true;at=true}local S={never=true}local function B(q,Z,S)Z=Z or"\065\115\115\101\114\116\105\111\110 \102\097\105\108\101\100\033"S=S or 1 if not q then error(Z,S+1)end end local function C(q,Z)return function(S,...)if S==q then return Z(q,...)else return Z(q,S,...)end end end local function Y(q,Z,S)if q then return Z else return S end end function q.new(Z)local S={value=Z;successCondition=true,condition=false,matchers={};_boundMatchers={}}setmetatable(S,q)S.a=C(S,S.a)S.an=S.a S.ok=C(S,S.ok)S.equal=C(S,S.equal)S.throw=C(S,S.throw)S.near=C(S,S.near)return S end function q.checkMatcherNameCollisions(B)if Z[B]or S[B]or q[B]then return false end return true end function q.extend(Z,q)Z.matchers=q or{}for q,S in pairs(Z.matchers)do Z._boundMatchers[q]=C(Z,function(q,...)local C=S(Z.value,...)local Y=C.pass==Z.successCondition B(Y,C.message,3)Z:_resetModifiers()return Z end)end return Z end function q.__index(B,C)if Z[C]then return B end if S[C]then local Z=(q.new(B.value)):extend(B.matchers)Z.successCondition=not B.successCondition return Z end if B._boundMatchers[C]then return B._boundMatchers[C]end return q[C]end function q._resetModifiers(q)q.successCondition=true end function q.a(Z,q)local S=(type(Z.value)==q)==Z.successCondition local C=Y(Z.successCondition,("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \111\102 \116\121\112\101 \037\113\044 \103\111\116 \118\097\108\117\101 \037\113 \111\102 \116\121\112\101 \037\115"):format(q,tostring(Z.value),type(Z.value)),("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \110\111\116 \111\102 \116\121\112\101 \037\113\044 \103\111\116 \118\097\108\117\101 \037\113 \111\102 \116\121\112\101 \037\115"):format(q,tostring(Z.value),type(Z.value)))B(S,C,3)Z:_resetModifiers()return Z end q.an=q.a function q.ok(q)local Z=(q.value~=nil)==q.successCondition local S=Y(q.successCondition,("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \037\113 \116\111 \098\101 \110\111\110-\110\105\108"):format(tostring(q.value)),("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \037\113 \116\111 \098\101 \110\105\108"):format(tostring(q.value)))B(Z,S,3)q:_resetModifiers()return q end function q.equal(Z,q)local S=(Z.value==q)==Z.successCondition local C=Y(Z.successCondition,("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \037\113 \040\037\115\041\044 \103\111\116 \037\113 \040\037\115\041 \105\110\115\116\101\097\100"):format(tostring(q),type(q),tostring(Z.value),type(Z.value)),("\069\120\112\101\099\116\101\100 \097\110\121\116\104\105\110\103 \098\117\116 \118\097\108\117\101 \037\113 \040\037\115\041"):format(tostring(q),type(q)))B(S,C,3)Z:_resetModifiers()return Z end function q.near(S,q,Z)assert(type(S.value)=="\110\117\109\098\101\114","\069\120\112\101\099\116\097\116\105\111\110 \118\097\108\117\101 \109\117\115\116 \098\101 \097 \110\117\109\098\101\114 \116\111 \117\115\101 \'\110\101\097\114\'")assert(type(q)=="\110\117\109\098\101\114","\111\116\104\101\114\086\097\108\117\101 \109\117\115\116 \098\101 \097 \110\117\109\098\101\114")assert(type(Z)=="\110\117\109\098\101\114"or Z==nil,"\108\105\109\105\116 \109\117\115\116 \098\101 \097 \110\117\109\098\101\114 \111\114 \110\105\108")Z=Z or 1e-07 local C=(math.abs(S.value-q)<=Z)==S.successCondition local O=Y(S.successCondition,("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \116\111 \098\101 \110\101\097\114 \037\102 \040\119\105\116\104\105\110 \037\102\041 \098\117\116 \103\111\116 \037\102 \105\110\115\116\101\097\100"):format(q,Z,S.value),("\069\120\112\101\099\116\101\100 \118\097\108\117\101 \116\111 \110\111\116 \098\101 \110\101\097\114 \037\102 \040\119\105\116\104\105\110 \037\102\041 \098\117\116 \103\111\116 \037\102 \105\110\115\116\101\097\100"):format(q,Z,S.value))B(C,O,3)S:_resetModifiers()return S end function q.throw(Z,q)local S,C=pcall(Z.value)local O=S~=Z.successCondition if q and not S then if Z.successCondition then O=C:find(q,1,true)~=nil else O=C:find(q,1,true)==nil end end local D if q then D=Y(Z.successCondition,("\069\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110 \116\111 \116\104\114\111\119 \097\110 \101\114\114\111\114 \099\111\110\116\097\105\110\105\110\103 \037\113\044 \098\117\116 \105\116 \037\115"):format(q,C and("\116\104\114\101\119\058 \037\115"):format(C)or"\100\105\100 \110\111\116 \116\104\114\111\119\046"),("\069\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110 \116\111 \110\101\118\101\114 \116\104\114\111\119 \097\110 \101\114\114\111\114 \099\111\110\116\097\105\110\105\110\103 \037\113\044 \098\117\116 \105\116 \116\104\114\101\119\058 \037\115"):format(q,tostring(C)))else D=Y(Z.successCondition,"\069\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110 \116\111 \116\104\114\111\119 \097\110 \101\114\114\111\114\044 \098\117\116 \105\116 \100\105\100 \110\111\116 \116\104\114\111\119\046",("\069\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110 \116\111 \115\117\099\099\101\101\100\044 \098\117\116 \105\116 \116\104\114\101\119 \097\110 \101\114\114\111\114\058 \037\115"):format(tostring(C)))end B(O,D,3)Z:_resetModifiers()return Z end return q
--[[
	Allows creation of expectation statements designed for behavior-driven
	testing (BDD). See Chai (JS) or RSpec (Ruby) for examples of other BDD
	frameworks.

	The Expectation class is exposed to tests as a function called `expect`:

		expect(5).to.equal(5)
		expect(foo()).to.be.ok()

	Expectations can be negated using .never:

		expect(true).never.to.equal(false)

	Expectations throw errors when their conditions are not met.
]]

local Expectation = {}

--[[
	These keys don't do anything except make expectations read more cleanly
]]
local SELF_KEYS = {
	to = true,
	be = true,
	been = true,
	have = true,
	was = true,
	at = true,
}

--[[
	These keys invert the condition expressed by the Expectation.
]]
local NEGATION_KEYS = {
	never = true,
}

--[[
	Extension of Lua's 'assert' that lets you specify an error level.
]]
local function assertLevel(condition, message, level)
	message = message or "Assertion failed!"
	level = level or 1

	if not condition then
		error(message, level + 1)
	end
end

--[[
	Returns a version of the given method that can be called with either . or :
]]
local function bindSelf(self, method)
	return function(firstArg, ...)
		if firstArg == self then
			return method(self, ...)
		else
			return method(self, firstArg, ...)
		end
	end
end

local function formatMessage(result, trueMessage, falseMessage)
	if result then
		return trueMessage
	else
		return falseMessage
	end
end

--[[
	Create a new expectation
]]
function Expectation.new(value)
	local self = {
		value = value,
		successCondition = true,
		condition = false,
		matchers = {},
		_boundMatchers = {},
	}

	setmetatable(self, Expectation)

	self.a = bindSelf(self, self.a)
	self.an = self.a
	self.ok = bindSelf(self, self.ok)
	self.equal = bindSelf(self, self.equal)
	self.throw = bindSelf(self, self.throw)
	self.near = bindSelf(self, self.near)

	return self
end

function Expectation.checkMatcherNameCollisions(name)
	if SELF_KEYS[name] or NEGATION_KEYS[name] or Expectation[name] then
		return false
	end

	return true
end

function Expectation:extend(matchers)
	self.matchers = matchers or {}

	for name, implementation in pairs(self.matchers) do
		self._boundMatchers[name] = bindSelf(self, function(_self, ...)
			local result = implementation(self.value, ...)
			local pass = result.pass == self.successCondition

			assertLevel(pass, result.message, 3)
			self:_resetModifiers()
			return self
		end)
	end

	return self
end

function Expectation.__index(self, key)
	-- Keys that don't do anything except improve readability
	if SELF_KEYS[key] then
		return self
	end

	-- Invert your assertion
	if NEGATION_KEYS[key] then
		local newExpectation = Expectation.new(self.value):extend(self.matchers)
		newExpectation.successCondition = not self.successCondition

		return newExpectation
	end

	if self._boundMatchers[key] then
		return self._boundMatchers[key]
	end

	-- Fall back to methods provided by Expectation
	return Expectation[key]
end

--[[
	Called by expectation terminators to reset modifiers in a statement.

	This makes chains like:

		expect(5)
			.never.to.equal(6)
			.to.equal(5)

	Work as expected.
]]
function Expectation:_resetModifiers()
	self.successCondition = true
end

--[[
	Assert that the expectation value is the given type.

	expect(5).to.be.a("number")
]]
function Expectation:a(typeName)
	local result = (type(self.value) == typeName) == self.successCondition

	local message = formatMessage(self.successCondition,
		("Expected value of type %q, got value %q of type %s"):format(
			typeName,
			tostring(self.value),
			type(self.value)
		),
		("Expected value not of type %q, got value %q of type %s"):format(
			typeName,
			tostring(self.value),
			type(self.value)
		)
	)

	assertLevel(result, message, 3)
	self:_resetModifiers()

	return self
end

-- Make alias public on class
Expectation.an = Expectation.a

--[[
	Assert that our expectation value is truthy
]]
function Expectation:ok()
	local result = (self.value ~= nil) == self.successCondition

	local message = formatMessage(self.successCondition,
		("Expected value %q to be non-nil"):format(
			tostring(self.value)
		),
		("Expected value %q to be nil"):format(
			tostring(self.value)
		)
	)

	assertLevel(result, message, 3)
	self:_resetModifiers()

	return self
end

--[[
	Assert that our expectation value is equal to another value
]]
function Expectation:equal(otherValue)
	local result = (self.value == otherValue) == self.successCondition

	local message = formatMessage(self.successCondition,
		("Expected value %q (%s), got %q (%s) instead"):format(
			tostring(otherValue),
			type(otherValue),
			tostring(self.value),
			type(self.value)
		),
		("Expected anything but value %q (%s)"):format(
			tostring(otherValue),
			type(otherValue)
		)
	)

	assertLevel(result, message, 3)
	self:_resetModifiers()

	return self
end

--[[
	Assert that our expectation value is equal to another value within some
	inclusive limit.
]]
function Expectation:near(otherValue, limit)
	assert(type(self.value) == "number", "Expectation value must be a number to use 'near'")
	assert(type(otherValue) == "number", "otherValue must be a number")
	assert(type(limit) == "number" or limit == nil, "limit must be a number or nil")

	limit = limit or 1e-7

	local result = (math.abs(self.value - otherValue) <= limit) == self.successCondition

	local message = formatMessage(self.successCondition,
		("Expected value to be near %f (within %f) but got %f instead"):format(
			otherValue,
			limit,
			self.value
		),
		("Expected value to not be near %f (within %f) but got %f instead"):format(
			otherValue,
			limit,
			self.value
		)
	)

	assertLevel(result, message, 3)
	self:_resetModifiers()

	return self
end

--[[
	Assert that our functoid expectation value throws an error when called.
	An optional error message can be passed to assert that the error message
	contains the given value.
]]
function Expectation:throw(messageSubstring)
	local ok, err = pcall(self.value)
	local result = ok ~= self.successCondition

	if messageSubstring and not ok then
		if self.successCondition then
			result = err:find(messageSubstring, 1, true) ~= nil
		else
			result = err:find(messageSubstring, 1, true) == nil
		end
	end

	local message

	if messageSubstring then
		message = formatMessage(self.successCondition,
			("Expected function to throw an error containing %q, but it %s"):format(
				messageSubstring,
				err and ("threw: %s"):format(err) or "did not throw."
			),
			("Expected function to never throw an error containing %q, but it threw: %s"):format(
				messageSubstring,
				tostring(err)
			)
		)
	else
		message = formatMessage(self.successCondition,
			"Expected function to throw an error, but it did not throw.",
			("Expected function to succeed, but it threw an error: %s"):format(
				tostring(err)
			)
		)
	end

	assertLevel(result, message, 3)
	self:_resetModifiers()

	return self
end

return Expectation
