Category talk:Wren-test
<lang ecmascript>/**
* module.wren with class order rearranged by PureFox to be compilable under Wren 0.3.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)
}</lang>