Skip to content

Commit

Permalink
Merge 77c6878 into df00128
Browse files Browse the repository at this point in the history
  • Loading branch information
benbrimeyer committed Oct 1, 2020
2 parents df00128 + 77c6878 commit eed4f84
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 10 deletions.
31 changes: 29 additions & 2 deletions src/Expectation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,33 @@ function Expectation.new(value)
return self
end

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

return true
end

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

for name, implementation in pairs(self.matchers) do
assert(name:sub(1, 1) ~= "_", string.format("Cannot write matcher %q. Matchers cannot start with %q", name, "_"))
assert(self:checkMatcherNameCollisions(name), string.format("Cannot overwrite matcher %q; it already exists", name))
self[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
Expand All @@ -99,7 +126,7 @@ function Expectation.__index(self, key)

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

return newExpectation
Expand Down Expand Up @@ -274,4 +301,4 @@ function Expectation:throw(messageSubstring)
return self
end

return Expectation
return Expectation
38 changes: 38 additions & 0 deletions src/ExpectationContext.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
local Expectation = require(script.Parent.Expectation)

local function copy(t)
local result = {}

for key, value in pairs(t) do
result[key] = value
end

return result
end

local ExpectationContext = {}
ExpectationContext.__index = ExpectationContext

function ExpectationContext.new(parent)
local self = {
_extensions = parent and copy(parent._extensions) or {},
}

return setmetatable(self, ExpectationContext)
end

function ExpectationContext:startExpectationChain(...)
return Expectation.new(...):extend(self._extensions)
end

function ExpectationContext:extend(config)
for key, value in pairs(config) do
if self._extensions[key] then
error(string.format("Cannot reassign %q in expect.extend", key))
end

self._extensions[key] = value
end
end

return ExpectationContext
10 changes: 9 additions & 1 deletion src/TestPlan.lua
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,15 @@ local function newEnvironment(currentNode, extraEnvironment)
env.fdescribe = env.describeFOCUS
env.xdescribe = env.describeSKIP

env.expect = Expectation.new
env.expect = setmetatable({
extend = function(...)
error(string.format("Cannot call %q from within a %q node.", "expect.extend", "describe"))
end,
}, {
__call = function(_self, ...)
return Expectation.new(...)
end,
})

return env
end
Expand Down
17 changes: 13 additions & 4 deletions src/TestRunner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
state is contained inside a TestSession object.
]]

local Expectation = require(script.Parent.Expectation)
local TestEnum = require(script.Parent.TestEnum)
local TestSession = require(script.Parent.TestSession)
local LifecycleHooks = require(script.Parent.LifecycleHooks)
Expand All @@ -17,8 +16,16 @@ local TestRunner = {
environment = {}
}

function TestRunner.environment.expect(...)
return Expectation.new(...)
local function wrapExpectContextWithPublicApi(expectationContext)
return setmetatable({
extend = function(...)
expectationContext:extend(...)
end,
}, {
__call = function(_self, ...)
return expectationContext:startExpectationChain(...)
end,
})
end

--[[
Expand Down Expand Up @@ -70,6 +77,8 @@ function TestRunner.runPlanNode(session, planNode, lifecycleHooks)
errorMessage = messagePrefix .. message .. "\n" .. debug.traceback()
end

testEnvironment.expect = wrapExpectContextWithPublicApi(session:getExpectationContext())

local context = session:getContext()

local nodeSuccess, nodeResult = xpcall(
Expand Down Expand Up @@ -174,4 +183,4 @@ function TestRunner.runPlanNode(session, planNode, lifecycleHooks)
lifecycleHooks:popHooks()
end

return TestRunner
return TestRunner
19 changes: 16 additions & 3 deletions src/TestSession.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
local TestEnum = require(script.Parent.TestEnum)
local TestResults = require(script.Parent.TestResults)
local Context = require(script.Parent.Context)
local ExpectationContext = require(script.Parent.ExpectationContext)

local TestSession = {}

Expand All @@ -25,6 +26,7 @@ function TestSession.new(plan)
results = TestResults.new(plan),
nodeStack = {},
contextStack = {},
expectationContextStack = {},
hasFocusNodes = false
}

Expand Down Expand Up @@ -98,12 +100,16 @@ end
function TestSession:pushNode(planNode)
local node = TestResults.createNode(planNode)
local lastNode = self.nodeStack[#self.nodeStack] or self.results
local lastContext = self.contextStack[#self.contextStack]
local context = Context.new(lastContext)

table.insert(lastNode.children, node)
table.insert(self.nodeStack, node)

local lastContext = self.contextStack[#self.contextStack]
local context = Context.new(lastContext)
table.insert(self.contextStack, context)

local lastExpectationContext = self.expectationContextStack[#self.expectationContextStack]
local expectationContext = ExpectationContext.new(lastExpectationContext)
table.insert(self.expectationContextStack, expectationContext)
end

--[[
Expand All @@ -113,6 +119,7 @@ function TestSession:popNode()
assert(#self.nodeStack > 0, "Tried to pop from an empty node stack!")
table.remove(self.nodeStack, #self.nodeStack)
table.remove(self.contextStack, #self.contextStack)
table.remove(self.expectationContextStack, #self.expectationContextStack)
end

--[[
Expand All @@ -123,6 +130,12 @@ function TestSession:getContext()
return self.contextStack[#self.contextStack]
end


function TestSession:getExpectationContext()
assert(#self.expectationContextStack > 0, "Tried to get expectationContext from an empty stack!")
return self.expectationContextStack[#self.expectationContextStack]
end

--[[
Tells whether the current test we're in should be skipped.
]]
Expand Down
25 changes: 25 additions & 0 deletions tests/failing/expectExtend-context-duplicate.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- luacheck: globals describe beforeAll expect

local noOptMatcher = function(_received, _expected)
return {
message = "",
pass = true,
}
end

return function()
beforeAll(function()
expect.extend({
customMatcher = noOptMatcher,
})
end)

describe("redefine matcher", function()
beforeAll(function()
expect.extend({
-- This should throw since we are redefining the same matcher
customMatcher = noOptMatcher,
})
end)
end)
end
16 changes: 16 additions & 0 deletions tests/failing/expectExtend-describe.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- luacheck: globals describe expect

local noOptMatcher = function(_received, _expected)
return {
message = "",
pass = true,
}
end

return function()
describe("SHOULD NOT work in a describe block", function()
expect.extend({
test = noOptMatcher,
})
end)
end
64 changes: 64 additions & 0 deletions tests/passing/expectExtend-context.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- luacheck: globals describe beforeAll expect it

local noOptMatcher = function(_received, _expected)
return {
message = "",
pass = true,
}
end

return function()
beforeAll(function()
expect.extend({
scope_0 = noOptMatcher,
})
end)

it("SHOULD inherit from previous beforeAll", function()
assert(expect().scope_0, "should have scope_0")
end)

describe("scope 1", function()
beforeAll(function()
expect.extend({
scope_1 = noOptMatcher,
})
end)

it("SHOULD inherit from previous beforeAll", function()
assert(expect().scope_1, "should have scope_1")
end)

it("SHOULD inherit from previous root level beforeAll", function()
assert(expect().scope_0, "should have scope_0")
end)

it("SHOULD NOT inherit scope 2", function()
assert(expect().scope_2 == nil, "should not have scope_0")
end)

describe("scope 2", function()
beforeAll(function()
expect.extend({
scope_2 = noOptMatcher,
})
end)

it("SHOULD inherit from previous beforeAll in scope 2", function()
assert(expect().scope_2, "should have scope_2")
end)

it("SHOULD inherit from previous beforeAll in scope 1", function()
assert(expect().scope_1, "should have scope_1")
end)

it("SHOULD inherit from previous beforeAll in scope 0", function()
assert(expect().scope_0, "should have scope_0")
end)
end)
end)

it("SHOULD NOT inherit from scope 1", function()
assert(expect("test").scope_1 == nil, "should not have scope_1")
end)
end
68 changes: 68 additions & 0 deletions tests/passing/expectExtend-illegalWrites.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
-- luacheck: globals describe beforeAll expect it

local noOptMatcher = function(_received, _expected)
return {
message = "",
pass = true,
}
end

local ERROR_CANNOT_OVERWRITE = "Cannot overwrite matcher"
local ERROR_CANNOT_START_WITH = "Matchers cannot start with"

local runTest = function(expectedError)
return function()
local success, message = pcall(function()
expect()
end)

assert(success == false, "should have been thrown")
assert(message:match(expectedError), string.format("\nUnexpected error:\n%s", message))
end
end

return function()
describe("attempt to overwrite default", function()
beforeAll(function()
expect.extend({
-- This should throw since `ok` is a default matcher
ok = noOptMatcher,
})
end)

it("SHOULD fail with expected error message", runTest(ERROR_CANNOT_OVERWRITE))
end)

describe("attempt to overwrite never", function()
beforeAll(function()
expect.extend({
-- This should throw since `never` is protected
never = noOptMatcher,
})
end)

it("SHOULD fail with expected error message", runTest(ERROR_CANNOT_OVERWRITE))
end)

describe("attempt to overwrite self", function()
beforeAll(function()
expect.extend({
-- This should throw since `a` is protected
a = noOptMatcher,
})
end)

it("SHOULD fail with expected error message", runTest(ERROR_CANNOT_OVERWRITE))
end)

describe("attempt to start with _", function()
beforeAll(function()
expect.extend({
-- This should throw since this starts with _
_fooBar = noOptMatcher,
})
end)

it("SHOULD fail with expected error message", runTest(ERROR_CANNOT_START_WITH))
end)
end

0 comments on commit eed4f84

Please sign in to comment.