/**
* ''module.wren'' with class order rearranged by PureFox to be compilable under Wren 0.4.0
*/
/**
* The MIT License (MIT)
* Copyright (c) 2015 Gavin Schulz
*/
/**
* A class of matchers to use for making assertions.
*/
class BaseMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
_value = value
}
/**
* @return The value for which this matcher was constructed.
*/
value { _value }
/**
* Negates this matcher and returns itself so that it can be chained with
* other matchers:
*
* var matcher = Matchers.new("value")
* matcher.not.toEqual("string") // Passing expectation.
*
* @return This instance of the classes that received this method.
*/
not {
_negated = true
// Return this matcher to support chaining.
return this
}
/**
* Asserts that the value is of a given class.
*
* @param {Class} klass Class which the value should be an instacne of.
*/
toBe (klass) {
var message = "Expected " + _value.toString + " of class " +
_value.type.toString + " to be of class " + klass.toString
report_(_value is klass, message)
}
/**
* Asserts that the value is false.
*/
toBeFalse {
var message = "Expected " + _value.toString + " to be false"
report_(_value == false, message)
}
/**
* Asserts that the value is true.
*/
toBeTrue {
var message = "Expected " + _value.toString + " to be true"
report_(_value == true, message)
}
/**
* Asserts that the value is null.
*/
toBeNull {
var message = "Expected " + _value.toString + " to be null"
report_(_value == null, message)
}
/**
* Asserts that the value is equal to the given value.
*
* @param {*} other Object that this value should be equal to.
*/
toEqual (other) {
var message = "Expected " + _value.toString + " to equal " + other.toString
report_(_value == other, message)
}
report_ (result, message) {
result = _negated ? !result : result
var expectation = Expectation.new(result, message)
Fiber.yield(expectation)
}
/**
* Enforces that the value for this matcher instance is of a certain class. If
* the value is not of the specified type the current Fiber will be aborted
* with an error message.
*
* @param {Class} klass Type of which the value should be an instance.
*/
enforceClass_ (klass) {
if (!(value is klass)) {
Fiber.abort(value.toString + " was not a " + klass.toString)
}
}
}
/**
* A class of matchers for making assertions about Fibers.
*/
class FiberMatchers is BaseMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
super(value)
}
/**
* Assert that invoking this value as a fiber generated a runtime error.
*/
toBeARuntimeError {
enforceClass_(Fiber)
// Run the fiber to generate the possible error.
value.try()
var message = "Expected a runtime error but it did not occur"
report_(value.error != null, message)
}
/**
* Assert that invoking this value as a fiber generated a runtime error with
* the given message.
*
* @param {String} errorMessage Error message that should have been generated
* by the fiber.
*/
toBeARuntimeError (errorMessage) {
enforceClass_(Fiber)
// Run the fiber to generate the possible error.
while (!value.isDone) {
value.try()
}
if (value.error == null) {
var message = "Expected a runtime error but it did not occur"
report_(false, message)
} else {
var message = "Expected a runtime error with error: " + errorMessage +
" but got: " + value.error
report_(value.error == errorMessage, message)
}
}
/**
* Assert that the fiber is done.
*/
toBeDone {
enforceClass_(Fiber)
var message = "Expected the fiber to be done"
report_(value.isDone, message)
}
/**
* Assert that invoking this fiber yields the expected value(s).
*
* @param shouldYield
*/
/*toYield (shouldYield) {
enforceClass_(Fiber)
// If a bare value was passed coerce it into a list.
if (!(shouldYield is List)) { shouldYield = [shouldYield] }
var results = []
// Get all values that this fiber could yield.
while (!value.isDone) {
results.add(value.try())
}
// The last value yielded from any fiber before it finishes is null.
results.removeAt(results.size - 1)
if (value.error != null) {
var message = "Expected the fiber to yield `" + shouldYield.toString +
"` but instead got a runtime error with message: `" + value.error +
" and yielded `" + results.toString + "`"
report_(false, message)
} else {
var message = "Expected the fiber to yield `" + shouldYield.toString +
"` but instead it yielded `" + results.toString + "`"
report_(results.size == shouldYield.size, message)
}
}*/
}
class NumMatchers is FiberMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
super(value)
}
/**
* Assert that the value is greater than some value. This matcher works on any
* class that defines the `>` operator.
*/
toBeGreaterThan (other) {
report_(value > other, "Expected " + value.toString + " to be greater " +
"than " + other.toString)
}
/**
* Assert that the value is less than some value. This matcher works on any
* class that defines the `<` operator.
*/
toBeLessThan (other) {
report_(value < other, "Expected " + value.toString + " to be less than " +
other.toString)
}
/**
* Assert that the value is between two values. This matches works on any
* class that defines the `<` and `>` operator.
*/
toBeBetween (min, max) {
var message = "Expected " + value.toString + " to be between " +
min.toString + " and " + max.toString
report_(value > min && value < max, message)
}
}
/**
* A class of matchers for making assertions about ranges.
*/
class RangeMatchers is NumMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
super(value)
}
/**
* Assert that the value contains the given range.
*
* @param {Range} other The range that should be contained within the range
* represented by the value.
*/
toContain (other) {
enforceClass_(Range)
var result = rangeIsContainedBy_(value, other)
var message = "Expected " + value.toString + " to contain " + other.toString
report_(result, message)
}
/**
* Assert that the value is contained within the given range.
*
* @param {Range} other The range that should contain this range represented
* by the value.
*/
toBeContainedBy (other) {
enforceClass_(Range)
var result = rangeIsContainedBy_(other, value)
var message = "Expected " + value.toString + " to be contained by " +
other.toString
report_(result, message)
}
rangeIsContainedBy_ (parent, child) {
var parentTo = parent.isInclusive ? parent.to : (parent.to - 1)
var childTo = child.isInclusive ? child.to : (child.to - 1)
return (child.from >= parent.from) && (childTo <= parentTo)
}
}
class StubMatchers is RangeMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
super(value)
}
/**
* Assert that this stub was called at least once.
*/
toHaveBeenCalled {
enforceClass_(Stub)
var message = "Expected " + value.name + " to have been called"
report_(value.called, message)
}
/**
* Assert that this stub was called a certain number of times.
*
* @param {Num} times Number of times this stub should have been called.
*/
toHaveBeenCalled (times) {
enforceClass_(Stub)
var message = "Expected " + value.name + " to have been called " +
times.toString + " times but was called " + value.calls.count.toString +
" times"
report_(value.calls.count == times, message)
}
/**
* Assert that this stub was called with the given arguments.
*
* @param {Sequence[*]} args Arguments that the stub should have been called
* with.
*/
toHaveBeenCalledWith (args) {
enforceClass_(Stub)
for (call in value.calls) {
// Ignore any call lists that aren't the same size.
if (call.count == args.count) {
var i = 0
var argsEqual = call.all { |callArg|
i = i + 1
return callArg == args[i - 1]
}
if (argsEqual) {
report_(true, "")
return
}
}
}
var message = "Expected " + value.name + " to have been called with " +
args.toString + " but was never called. Calls were:\n " +
value.calls.join("\n ")
report_(false, message)
}
}
/**
* This class provides a way to create a stub function that can used in place of
* a real method with additional tracking and introspection capabilities.
*
* This class takes advantage of the `call` semantics of Wren to create a class
* that can be passed around like a function by virtue of defining the
* appropriate `call` methods for any number of allowed arguments.
*
* This class does not contain any matcher methods instead look at StubMatchers
* for matchers that work with Stub instances.
*
* A number of static constructor helper methods are provided to make stub
* creation more readable in context.
*/
class Stub {
/**
* Create a new Stub instance that returns nothing when invoked.
*
* @param {String} name Name of the stub instance.
*/
construct new (name) {
_name = name
_calls = []
}
/**
* Create a new Stub instance that calls the given function when invoked.
*
* @param {String} name Name of the stub instance.
* @param {Fn} fakeFn Function to call when this stub is invoked.
*/
construct new (name, fakeFn) {
_name = name
_fakeFn = fakeFn
_calls = []
}
/**
* Creates a Stub that calls the given fake function when called.
*
* @param {String} name Name of the stub instance.
* @param {Fn} fakeFn Function that should be called every time this stub is
* called.
* @return {Stub} Instance that calls the fake function when called with any
* number of arguments.
*/
static andCallFake (name, fakeFn) {
return Stub.new(name, fakeFn)
}
/**
* Creates a Stub that always returns the same value when called.
*
* @param {String} name Name of the stub instance.
* @param {*} returnValue Value that should be returned when this stub is
* called.
* @return {Stub} Instance that returns a value when called with any number of
* arguments.
*/
static andReturnValue (name, returnValue) {
// Wrap the bare return value in a function to unify interfaces.
var valueReturningFn = Fn.new { |args| returnValue }
return Stub.new(name, valueReturningFn)
}
/**
* @return {Bool} Whether or not the stub has been called.
*/
called { _calls.count != 0 }
/**
* @return {Sequence[Sequence[*]]} List of lists containing the arguments that
* each call to this stub provided.
*/
calls { _calls }
/**
* @return {Sequence[*]} List of arguments for the first call on this stub.
*/
firstCall {
if (_calls.count > 0) {
return _calls[0]
}
}
/**
* @return {Sequence[*]} List of arguments for the most recent call on this
* stub.
*/
mostRecentCall {
if (_calls.count > 0) {
return _calls[_calls.count - 1]
}
}
/**
* @return {String} Name of the stub instance.
*/
name { _name }
/**
* Clears all tracking for this stub.
*/
reset {
_calls = []
}
call {
_calls.add([])
if (_fakeFn) {
return _fakeFn.call([])
}
}
call () {
_calls.add([])
if (_fakeFn) {
return _fakeFn.call([])
}
}
call (a) {
_calls.add([a])
if (_fakeFn) {
return _fakeFn.call([a])
}
}
call (a, b) {
_calls.add([a, b])
if (_fakeFn) {
return _fakeFn.call([a, b])
}
}
call (a, b, c) {
_calls.add([a, b, c])
if (_fakeFn) {
return _fakeFn.call([a, b, c])
}
}
call (a, b, c, d) {
_calls.add([a, b, c, d])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d])
}
}
call (a, b, c, d, e) {
_calls.add([a, b, c, d, e])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e])
}
}
call (a, b, c, d, e, f) {
_calls.add([a, b, c, d, e, f])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f])
}
}
call (a, b, c, d, e, f, g) {
_calls.add([a, b, c, d, e, f, g])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g])
}
}
call (a, b, c, d, e, f, g, h) {
_calls.add([a, b, c, d, e, f, g, h])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h])
}
}
call (a, b, c, d, e, f, g, h, i) {
_calls.add([a, b, c, d, e, f, g, h, i])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i])
}
}
call (a, b, c, d, e, f, g, h, i, j) {
_calls.add([a, b, c, d, e, f, g, h, i, j])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j])
}
}
call (a, b, c, d, e, f, g, h, i, j, k) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k])
}
}
call (a, b, c, d, e, f, g, h, i, j, k, l) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k, l])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k, l])
}
}
call (a, b, c, d, e, f, g, h, i, j, k, l, m) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k, l, m])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k, l, m])
}
}
call (a, b, c, d, e, f, g, h, i, j, k, l, m, n) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k, l, m, n])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k, l, m, n])
}
}
call (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o])
}
}
call (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {
_calls.add([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p])
if (_fakeFn) {
return _fakeFn.call([a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p])
}
}
}
/**
* Run a test block.
*/
class Runnable {
/**
* Create a new runnable test object. Either a Fiber or Fn can be given as the
* runnable object.
*
* @param {String} title Name of the test.
* @param {Sequence[Fn|Fiber]} beforeEaches List of functions or fibers that
* should be called before the main
* test block is run.
* @param {Sequence[Fn|Fiber]} afterEaches List of functions or fibers that
* should be called after the main
* test block is run.
* @param {Fiber|Fn} body Fiber or function that represents the test to run.
*/
construct new (title, beforeEaches, afterEaches, fn) {
_title = title
_beforeEaches = beforeEaches
_afterEaches = afterEaches
_expectations = []
// Wrap bare functions in Fibers.
if (fn.type != Fiber) {
fn = Fiber.new(fn)
}
_fn = fn
}
/**
* @return {Num} Elapsed time for this test, in milliseconds, including
* running all defined `beforeEach` and `afterEach` methods.
*/
duration { (_duration * 1000).ceil }
/**
* @return {String} The error string of this Runnable if an error was
* encountered while running this test.
*/
error { _fn.error }
/**
* @return {Sequence[Expectations]} List of `Expectation`s that were emitted
* by the test body.
*/
expectations { _expectations }
/**
* @return {Bool} Whether this Runnable instance has been run.
*/
hasRun { _fn.isDone }
/**
* Runs the test function and collects the `Expectation`s that were generated.
*
* @return {Sequence[Expectation]} List of `Expectation`s that were emitted by
* the test body.
*/
run() {
var startTime = System.clock
for (fn in _beforeEaches) { fn.call() }
while (!_fn.isDone) {
var result = _fn.try()
// Ignore any values that were yielded that weren't an Expectation.
// Note: When a fiber is finished the last `yield` invocation returns
// `null` so it will not be added to the array.
if (result is Expectation) {
_expectations.add(result)
}
}
for (fn in _afterEaches) { fn.call() }
_duration = System.clock - startTime
return _expectations
}
/**
* @return {String} Title string of this Runnable.
*/
title { _title }
}
/**
* Represents a skipped test or suite and implements the same basic interface as
* `Runnable`.
*/
class Skippable {
/**
* Create a new skipped test or suite.
*
* @param {String} title Name of the skipped test or suite.
*/
construct new (title) {
_title = title
}
run { /* Do nothing. */ }
/**
* @return {String} Title string of this Skippable.
*/
title { _title }
}
/**
* An Expectation captures an assertion about a value made in a test block. It
* is used by the default matchers to communicate the pass/fail state of a test
* block and can be used by other matcher implementations if you need to extend
* the deafault matchers.
*/
class Expectation {
/**
* Create a new expectation result instance.
*
* @param {Bool} passed Whether this expectation was successful.
* @param {String} message Message to print if the expectation was not
* successful.
*/
construct new(passed, message) {
_passed = passed
_message = message
}
/**
* @return {Bool} Whether or not this expectation was successful.
*/
passed { _passed }
/**
* @return {String} Message that explains the failure mode of this
* expectation.
*/
message { _message }
}
class Suite {
/**
* Create a new suite of tests.
*
* @param {String} name Name of the suite.
* @param {Fn} block Function that defines the set of tests that belong to
* this suite. It receives this instance as its first
* argument.
*/
construct new (name, block) {
constructor_(name, [], [], block)
}
/**
* Create a new suite of tests with the given `beforeEach` and `afterEach`
* functions.
*
* @param {String} name Name of the suite.
* @param {Sequence[Fn]} beforeEaches A list of functions to invoke before
* each test is invoked.
* @param {Sequence[Fn]} afterEaches A list of functions to invoke after each
* test is invoked.
* @param {Fn} block Function that defines the set of tests that belong to
* this suite. It receives this instance as its first
* argument.
*/
construct new (name, beforeEaches, afterEaches, block) {
constructor_(name, beforeEaches, afterEaches, block)
}
/**
* Stub method used when skipping an `afterEach` block.
*/
afterEach { this }
/**
* Define a block to run after every test in this suite and any nested suites.
*
* @param {Fn} block Function that should be run after every test.
*/
afterEach (block) {
_afterEaches.add(block)
}
/**
* Stub method used when skipping a `beforeEach` block.
*/
beforeEach { this }
/**
* Define a block to run before every test in this suite and any nested
* suites.
*
* @param {Fn} block Function that should be run before every test.
*/
beforeEach (block) {
_beforeEaches.add(block)
}
run (reporter) {
reporter.suiteStart(title)
for (runnable in _runnables) {
if (runnable is Suite) {
runnable.run(reporter)
} else if (runnable is Skippable) {
reporter.runnableSkipped(runnable)
} else {
reporter.testStart(runnable)
var result = runnable.run()
var passed = result.all { |r| r.passed }
if (runnable.error) {
reporter.testError(runnable)
} else if (passed) {
reporter.testPassed(runnable)
} else {
reporter.testFailed(runnable)
}
reporter.testEnd(runnable)
}
}
reporter.suiteEnd(title)
}
/**
* Stub method used when skipping a `should` block inside the suite.
*
* @param {String} name Descriptive name for the test.
*/
should (name) {
var skippable = Skippable.new(name)
_runnables.add(skippable)
return this
}
/**
* Create a new test block.
*
* @param {String} name Descriptive name for the test.
* @param {Fn|Fiber} block Function or fiber block that should be executed for
* this test.
*/
should (name, block) {
var runnable = Runnable.new(name, _beforeEaches, _afterEaches, block)
_runnables.add(runnable)
}
/**
* Does nothing except receive the block that would normally be associated
* with the construct that was skipped.
*/
skip (block) { /* Do nothing */ }
/**
* Stub method used when skipping a `suite` block inside the suite.
*
* @param {String} name Name of the suite.
*/
suite (name) {
var skippable = Skippable.new(name)
_runnables.add(skippable)
return this
}
/**
* Create a new suite of tests that are nested under this suite.
*
* @param {String} name Name of the suite.
* @param {Fn} block Function that defines the set of tests that belong to
* this suite.
*/
suite (name, block) {
var suite = Suite.new(name, _beforeEaches, _afterEaches, block)
_runnables.add(suite)
}
/**
* @return {String} Title string of this suite.
*/
title { _name }
constructor_ (name, beforeEaches, afterEaches, block) {
_name = name
_beforeEaches = beforeEaches
_afterEaches = afterEaches
_runnables = []
// Invoke the block that defines the tests in this suite.
block.call(this)
}
}
/**
* Defines the full interface for a test reporter.
*/
class Reporter {
/**
* Called when a test run is entirely finished and can be used to print a test
* summary for instance.
*/
epilogue () {}
/**
* Called when a runnable is skipped.
*
* @param {Skippable} skippable Skippable object that represents the runnable
* that was skipped.
*/
runnableSkipped (skippable) {}
/**
* Called when a suite run is started.
*
* @param {String} title Name of the suite that has been started.
*/
suiteStart (title) {}
/**
* Called when a suite run is finished.
*
* @param {String} title Name of the suite that has been finished.
*/
suiteEnd (title) {}
/**
* Called when a test is started.
*
* @param {Runnable} runnable Runnable object that is about to be run.
*/
testStart (runnable) {}
/**
* Called when a test passed.
*
* @param {Runnable} runnable Runnable object that was successful.
*/
testPassed (runnable) {}
/**
* Called when a test failed.
*
* @param {Runnable} runnable Runnable object that failed.
*/
testFailed (runnable) {}
/**
* Called when a test encounters an error.
*
* @param {Runnable} runnable Runnable object that encountered an error.
*/
testError (runnable) {}
/**
* Called when a test is finished.
*
* @param {Runnable} runnable Runnable object that just finished.
*/
testEnd (runnable) {}
}
/**
* A test reporter that outputs the results to the console.
*/
class ConsoleReporter is Reporter {
construct new() {
_indent = 0
// Count the different kinds of tests reported.
_counters = {
"tests": 0,
"passed": 0,
"failed": 0,
"errors": 0,
"skipped": 0
}
_startTime = System.clock
}
getCount_ (kind) { _counters[kind].toString }
count_ (kind) {
_counters[kind] = _counters[kind] + 1
}
/**
* Prints out a summary of the test run reported on by this instance.
*/
epilogue () {
var duration = ((System.clock - _startTime) * 1000).ceil.toString
System.print("")
System.print("==== Tests Summary ====")
var result = getCount_("tests") + " tests, " + getCount_("passed") +
" passed, " + getCount_("failed") + " failed, " + getCount_("errors") +
" errors, " + getCount_("skipped") + " skipped (" + duration + " ms)"
print_(result, 2)
}
runnableSkipped (skippable) {
count_("skipped")
print_("- " + skippable.title, _indent + 1,
"\u001b[36m")
}
suiteStart (title) {
_indent = _indent + 1
print_(title)
}
suiteEnd (title) {
_indent = _indent - 1
if (_indent == 0) { System.print("") }
}
testStart (runnable) {
_indent = _indent + 1
count_("tests")
}
testEnd (runnable) {
_indent = _indent - 1
}
testPassed (runnable) {
count_("passed")
print_(Symbols["ok"] + " \u001b[90mshould " + runnable.title, _indent,
"\u001b[32m")
}
testFailed (runnable) {
count_("failed")
print_(Symbols["err"] + " \u001b[90mshould " + runnable.title, _indent,
"\u001b[31m")
var failedExpectations = runnable.expectations.where { |e| !e.passed }
for (expectation in failedExpectations) {
print_(expectation.message, _indent + 1, "\u001b[31m")
}
}
testError (runnable) {
count_("errors")
print_(Symbols["err"] + " \u001b[90mshould " + runnable.title)
print_("Error: " + runnable.error, _indent + 1, "\u001b[31m")
}
print_ (string) {
print_(string, _indent)
}
print_ (string, indent) {
print_(string, indent, "")
}
print_ (string, indent, color) {
var result = ""
for (i in 2...(indent * 2)) {
result = result + " "
}
System.print(color + result + string + "\u001b[0m")
}
}
var Symbols = {
"ok": "✓",
"err": "✖"
}
// Create top-level class so that trying to access an undefined matcher doesn't
// result in leaking the implementation details of how our matcher classes are
// combined and create a potentially misleading error message:
// Error: StubMatchers does not implement 'toBeUndefined'
// This error message is misleading because this isn't a problem with the
// StubMatchers class instead the real problem is that none of the base matcher
// classes define the 'toBeUndefined' matcher. Utilizing this empty class will
// result in a more correct (and useful) error message if the user is accessing
// an undefined matcher:
// Error: Matchers does not implement 'toBeUndefinedMatcher'
class Matchers is StubMatchers {
/**
* Create a new `Matcher` object for a value.
*
* @param {*} value The value to be matched on.
*/
construct new (value) {
super(value)
}
}
/**
* Convenience method for creating new Matchers in a more readable style.
*
* @param {*} value Value to create a new matcher for.
* @return A new `Matchers` instance for the given value.
*/
var Expect = Fn.new { |value|
return Matchers.new(value)
}