window.Spec ||= {} window.Spec.WindowExtensions = {
SpecObject: (object) -> $.extend this, object if object this # Tests if matched value === expected value be: (expected) -> (value) -> [value is expected, "to be #{Spec.inspect expected}, actual #{Spec.inspect value}"] # Tests if matched value is a boolean beABoolean: (value) -> [typeof value is 'boolean', "to have type “boolean”, actual “#{typeof value}”"] # Tests if matched value is a function beAFunction: (value) -> [typeof value is 'function', "to have type “function”, actual “#{typeof value}”"] # Tests if matched value is a number beANumber: (value) -> [typeof value is 'number', "to have type “number”, actual “#{typeof value}”"] # Tests if matched value is a string beAString: (value) -> [typeof value is 'string', "to have type “string”, actual “#{typeof value}”"] # Tests if matched value is an instance of class beAnInstanceOf: (klass) -> (value) -> [value instanceof klass, "to be an instance of “#{klass}”"] # Tests if matched value is an object beAnObject: (value) -> [typeof value is 'object', "to have type “object”, actual “#{typeof value}”"] # Tests if given attribute is true beAttribute: (attribute) -> (value) -> result = value[attribute] result = result.call value if typeof result is 'function' [!!result, "to be #{attribute}"] # Tests if matched value is boolean false beFalse: (value) -> [String(value) == 'false', "to be false, got #{Spec.inspect value}"] # Adds a setup step to the current test case before: (action) -> test = Spec.testStack[Spec.testStack.length - 1] test.before.push action # Tests if matched value is boolean true beTrue: (value) -> [String(value) == 'true', "to be true, got #{Spec.inspect value}"] context: () -> describe.apply this, arguments # Prepares a sub-test of the current test case describe: (title, definition) -> parent = Spec.testStack[Spec.testStack.length - 1] ul = $('<ul></ul>') switch Spec.Format when 'ul' parent.ul.append($('<li>' + title + '</li>').append(ul)) when 'terminal' $('.results').append(Spec.pad(title, parent.ul.depth) + "<br>") ul.depth = parent.ul.depth + 2 Spec.testStack.push { title: title ul: ul before: [] } definition() Spec.testStack.pop() # Tests if matched value == expected value equal: (expected) -> (value) -> [String(value) == String(expected), "to match “#{$.trim diffString(String(value), String(expected))}”"] # Allows an assertion on a non-object value expect: (object) -> { to: (matcher) -> result = Spec.findMatcher(matcher)(object) Spec.fail "expected #{result[1]}" unless result[0] notTo: (matcher) -> result = Spec.findMatcher(matcher)(object) Spec.fail "expected not #{result[1]}" if result[0] } # Sets up an expectation expectation: (message) -> exp = { message: message meet: -> @met++ met: 0 desired: 1 twice: -> @desired = 2 this exactly: (times) -> @desired = times {times: this} timesString: (times) -> switch times when 0 'not at all' when 1 'once' when 2 'twice' else "#{times} times" check: -> if @met != @desired Spec.fail "expected #{message} #{@timesString @desired}, actually received #{@timesString @met}" } Spec.expectations.push exp exp finishTest: -> for expectation in Spec.expectations expectation.check() reportTestResult(if Spec.passed then "passed" else "failed") delete Spec.expectations delete Spec.testTitle window.onerror = -> null Spec.env.sandbox.empty().remove() # Syntactic sugar to create a before method that prepares a variable # # Example: # given 'dog', -> new Dog() given: (name, definition) -> before -> @[name] = definition.call Spec.env haveHtml: (expected) -> (value) -> div = $(document.createElement 'div') div.html expected normalized = div.html() actual = value.html() [actual == normalized, "to have html “#{$.trim diffString(actual, normalized)}”"] # All-purpose inclusion matcher include: (expected) -> if expected instanceof Array (value) -> match = true for test in expected match = false unless (value.indexOf && value.indexOf(test) >= 0) || value[test]? [match, "to include #{Spec.inspect expected}, actual #{Spec.inspect value}"] else if typeof expected == 'object' (value) -> missing = {} match = true for test of expected if expected.hasOwnProperty test unless value[test] isnt undefined && String(value[test]) == String(expected[test]) match = false missing[test] = expected[test] [match, "to include #{Spec.inspect expected}, actual #{Spec.inspect value}, missing #{Spec.inspect missing}"] else include([expected]) # Creates a specificaition it: (title, definition) -> # Automatically choose a title when only definition supplied if typeof title is 'function' definition = title title = Spec.descriptionize title test = Spec.testStack[Spec.testStack.length - 1] if definition? Spec.env = {sandbox: $('<div/>').appendTo document.body} for aTest in Spec.testStack for action in aTest.before action.call Spec.env Spec.expectations = [] Spec.testTitle = title window.onerror = (message, url, line) -> Spec.fail message, "#{url.replace(document.location, '')}:#{line}" Spec.passed = false finishTest() Spec.passed = true definition.call Spec.env finishTest() else reportTestResult "pending" # Creates a specification that tests an attribute of subject # # Example: # subject -> new Employee('Fred') # its 'name', -> should equal('Fred') its: (attribute, definition) -> it "#{attribute} #{Spec.descriptionize definition}", -> value = @subject[attribute] value = value.call @subject if typeof value is 'function' @subject = value definition.call Spec.env reportTestResult: (status) -> test = Spec.testStack[Spec.testStack.length - 1] switch Spec.Format when 'ul' test.ul.append '<li class="' + status + '">' + Spec.testTitle + '</li>' when 'terminal' s = Spec.testTitle color = switch status when 'passed' then 32 when 'failed' then 31 when 'pending' then 33 $('.results').append Spec.pad("[#{color}m#{s}[0m<br>", test.ul.depth) Spec.counts[status]++ Spec.counts.total++ # Runs a test against @subject # # Example # subject -> new Employee() # it -> should beAnInstanceOf(Employee) should: (matcher) -> expect(Spec.env.subject).to matcher # Runs a negative test against @subject # # Example # subject -> new Employee() # it -> shouldNot be(null) shouldNot: (matcher) -> expect(Spec.env.subject).notTo matcher # Creates a before method to prepare the @subject variable subject: (definition) -> given 'subject', definition
}