uki 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. data/.gitignore +7 -0
  2. data/.gitmodules +3 -0
  3. data/LICENSE +20 -0
  4. data/Rakefile +23 -0
  5. data/Readme.rdoc +9 -0
  6. data/VERSION +1 -0
  7. data/bin/uki +102 -0
  8. data/frameworks/jspec/lib/images/bg.png +0 -0
  9. data/frameworks/jspec/lib/images/hr.png +0 -0
  10. data/frameworks/jspec/lib/images/loading.gif +0 -0
  11. data/frameworks/jspec/lib/images/sprites.bg.png +0 -0
  12. data/frameworks/jspec/lib/images/sprites.png +0 -0
  13. data/frameworks/jspec/lib/images/vr.png +0 -0
  14. data/frameworks/jspec/lib/jspec.css +149 -0
  15. data/frameworks/jspec/lib/jspec.growl.js +115 -0
  16. data/frameworks/jspec/lib/jspec.jquery.js +72 -0
  17. data/frameworks/jspec/lib/jspec.js +1756 -0
  18. data/frameworks/jspec/lib/jspec.shell.js +39 -0
  19. data/frameworks/jspec/lib/jspec.timers.js +90 -0
  20. data/frameworks/jspec/lib/jspec.xhr.js +195 -0
  21. data/frameworks/uki/README.rdoc +179 -0
  22. data/frameworks/uki/compiler.jar +0 -0
  23. data/frameworks/uki/run.rb +2 -0
  24. data/frameworks/uki/spec/commands/example_command.rb +19 -0
  25. data/frameworks/uki/spec/dom.html +39 -0
  26. data/frameworks/uki/spec/support/images/bg.png +0 -0
  27. data/frameworks/uki/spec/support/images/hr.png +0 -0
  28. data/frameworks/uki/spec/support/images/loading.gif +0 -0
  29. data/frameworks/uki/spec/support/images/sprites.bg.png +0 -0
  30. data/frameworks/uki/spec/support/images/sprites.png +0 -0
  31. data/frameworks/uki/spec/support/images/vr.png +0 -0
  32. data/frameworks/uki/spec/support/jspec.css +149 -0
  33. data/frameworks/uki/spec/support/jspec.js +1773 -0
  34. data/frameworks/uki/spec/support/jspec.xhr.js +193 -0
  35. data/frameworks/uki/spec/support/spec.helper.js +1 -0
  36. data/frameworks/uki/spec/unit/background.spec.js +29 -0
  37. data/frameworks/uki/spec/unit/builder.spec.js +51 -0
  38. data/frameworks/uki/spec/unit/data/model.spec.js +29 -0
  39. data/frameworks/uki/spec/unit/dom/dnd.spec.js +71 -0
  40. data/frameworks/uki/spec/unit/dom/event.spec.js +78 -0
  41. data/frameworks/uki/spec/unit/dom.spec.js +28 -0
  42. data/frameworks/uki/spec/unit/geometry.spec.js +79 -0
  43. data/frameworks/uki/spec/unit/selector.spec.js +140 -0
  44. data/frameworks/uki/spec/unit/theme/template.spec.js +31 -0
  45. data/frameworks/uki/spec/unit/utils.spec.js +176 -0
  46. data/frameworks/uki/spec/unit/view/base.spec.js +86 -0
  47. data/frameworks/uki/spec/unit/view/container.spec.js +73 -0
  48. data/frameworks/uki/spec/unit/view.spec.js +13 -0
  49. data/frameworks/uki/src/airport.js +1 -0
  50. data/frameworks/uki/src/uki-core/attachment.js +175 -0
  51. data/frameworks/uki/src/uki-core/background/css.js +37 -0
  52. data/frameworks/uki/src/uki-core/background/cssBox.js +73 -0
  53. data/frameworks/uki/src/uki-core/background/multi.js +20 -0
  54. data/frameworks/uki/src/uki-core/background/null.js +10 -0
  55. data/frameworks/uki/src/uki-core/background/rows.js +77 -0
  56. data/frameworks/uki/src/uki-core/background/sliced9.js +206 -0
  57. data/frameworks/uki/src/uki-core/background.js +35 -0
  58. data/frameworks/uki/src/uki-core/builder.js +68 -0
  59. data/frameworks/uki/src/uki-core/collection.js +278 -0
  60. data/frameworks/uki/src/uki-core/const.js +17 -0
  61. data/frameworks/uki/src/uki-core/dom/dnd.js +93 -0
  62. data/frameworks/uki/src/uki-core/dom/event.js +194 -0
  63. data/frameworks/uki/src/uki-core/dom/nativeLayout.js +18 -0
  64. data/frameworks/uki/src/uki-core/dom/offset.js +130 -0
  65. data/frameworks/uki/src/uki-core/dom/w3cdnd.js +333 -0
  66. data/frameworks/uki/src/uki-core/dom.js +109 -0
  67. data/frameworks/uki/src/uki-core/geometry.js +658 -0
  68. data/frameworks/uki/src/uki-core/image.js +90 -0
  69. data/frameworks/uki/src/uki-core/selector.js +201 -0
  70. data/frameworks/uki/src/uki-core/theme/base.js +39 -0
  71. data/frameworks/uki/src/uki-core/theme/template.js +26 -0
  72. data/frameworks/uki/src/uki-core/theme.js +45 -0
  73. data/frameworks/uki/src/uki-core/uki.js +45 -0
  74. data/frameworks/uki/src/uki-core/utils.js +399 -0
  75. data/frameworks/uki/src/uki-core/view/base.js +480 -0
  76. data/frameworks/uki/src/uki-core/view/container.js +155 -0
  77. data/frameworks/uki/src/uki-core/view/focusable.js +93 -0
  78. data/frameworks/uki/src/uki-core/view/observable.js +66 -0
  79. data/frameworks/uki/src/uki-core/view/styleable.js +70 -0
  80. data/frameworks/uki/src/uki-core/view/utils.js +66 -0
  81. data/frameworks/uki/src/uki-core/view.js +21 -0
  82. data/frameworks/uki/src/uki-core.js +36 -0
  83. data/frameworks/uki/src/uki-data/data.js +1 -0
  84. data/frameworks/uki/src/uki-data/model.js +28 -0
  85. data/frameworks/uki/src/uki-data/observable.js +34 -0
  86. data/frameworks/uki/src/uki-data.js +1 -0
  87. data/frameworks/uki/src/uki-more/more/utils.js +20 -0
  88. data/frameworks/uki/src/uki-more/more/view/listContainer.js +4 -0
  89. data/frameworks/uki/src/uki-more/more/view/multiselectList.js +196 -0
  90. data/frameworks/uki/src/uki-more/more/view/radioButton.js +27 -0
  91. data/frameworks/uki/src/uki-more/more/view/splitTable.js +79 -0
  92. data/frameworks/uki/src/uki-more/more/view/toggleButton.js +24 -0
  93. data/frameworks/uki/src/uki-more/more/view/treeList/render.js +53 -0
  94. data/frameworks/uki/src/uki-more/more/view/treeList.js +110 -0
  95. data/frameworks/uki/src/uki-more/more/view.js +2 -0
  96. data/frameworks/uki/src/uki-more/more.js +1 -0
  97. data/frameworks/uki/src/uki-more.js +4 -0
  98. data/frameworks/uki/src/uki-theamless.js +15 -0
  99. data/frameworks/uki/src/uki-theme/airport/i/button/down-c.gif +0 -0
  100. data/frameworks/uki/src/uki-theme/airport/i/button/down-c.png +0 -0
  101. data/frameworks/uki/src/uki-theme/airport/i/button/down-h.gif +0 -0
  102. data/frameworks/uki/src/uki-theme/airport/i/button/down-h.png +0 -0
  103. data/frameworks/uki/src/uki-theme/airport/i/button/down-m.png +0 -0
  104. data/frameworks/uki/src/uki-theme/airport/i/button/down-v.png +0 -0
  105. data/frameworks/uki/src/uki-theme/airport/i/button/focusRing-c.png +0 -0
  106. data/frameworks/uki/src/uki-theme/airport/i/button/focusRing-h.png +0 -0
  107. data/frameworks/uki/src/uki-theme/airport/i/button/focusRing-m.png +0 -0
  108. data/frameworks/uki/src/uki-theme/airport/i/button/focusRing-v.png +0 -0
  109. data/frameworks/uki/src/uki-theme/airport/i/button/focusRing.png +0 -0
  110. data/frameworks/uki/src/uki-theme/airport/i/button/hover-c.gif +0 -0
  111. data/frameworks/uki/src/uki-theme/airport/i/button/hover-c.png +0 -0
  112. data/frameworks/uki/src/uki-theme/airport/i/button/hover-h.gif +0 -0
  113. data/frameworks/uki/src/uki-theme/airport/i/button/hover-h.png +0 -0
  114. data/frameworks/uki/src/uki-theme/airport/i/button/hover-m.png +0 -0
  115. data/frameworks/uki/src/uki-theme/airport/i/button/hover-v.png +0 -0
  116. data/frameworks/uki/src/uki-theme/airport/i/button/hover.png +0 -0
  117. data/frameworks/uki/src/uki-theme/airport/i/button/normal-c.gif +0 -0
  118. data/frameworks/uki/src/uki-theme/airport/i/button/normal-c.png +0 -0
  119. data/frameworks/uki/src/uki-theme/airport/i/button/normal-h.gif +0 -0
  120. data/frameworks/uki/src/uki-theme/airport/i/button/normal-h.png +0 -0
  121. data/frameworks/uki/src/uki-theme/airport/i/button/normal-m.png +0 -0
  122. data/frameworks/uki/src/uki-theme/airport/i/button/normal-v.png +0 -0
  123. data/frameworks/uki/src/uki-theme/airport/i/button/normal.png +0 -0
  124. data/frameworks/uki/src/uki-theme/airport/i/checkbox/checkbox.png +0 -0
  125. data/frameworks/uki/src/uki-theme/airport/i/checkbox/focus.png +0 -0
  126. data/frameworks/uki/src/uki-theme/airport/i/checkbox/normal.gif +0 -0
  127. data/frameworks/uki/src/uki-theme/airport/i/checkbox/normal.png +0 -0
  128. data/frameworks/uki/src/uki-theme/airport/i/panel/dark-h.gif +0 -0
  129. data/frameworks/uki/src/uki-theme/airport/i/panel/dark-h.png +0 -0
  130. data/frameworks/uki/src/uki-theme/airport/i/panel/dark-m.png +0 -0
  131. data/frameworks/uki/src/uki-theme/airport/i/panel/dark.png +0 -0
  132. data/frameworks/uki/src/uki-theme/airport/i/popup/normal.png +0 -0
  133. data/frameworks/uki/src/uki-theme/airport/i/radio/focus.png +0 -0
  134. data/frameworks/uki/src/uki-theme/airport/i/radio/normal.gif +0 -0
  135. data/frameworks/uki/src/uki-theme/airport/i/radio/normal.png +0 -0
  136. data/frameworks/uki/src/uki-theme/airport/i/radio/radio.png +0 -0
  137. data/frameworks/uki/src/uki-theme/airport/i/shadow/large-c.png +0 -0
  138. data/frameworks/uki/src/uki-theme/airport/i/shadow/large-h.png +0 -0
  139. data/frameworks/uki/src/uki-theme/airport/i/shadow/large-m.png +0 -0
  140. data/frameworks/uki/src/uki-theme/airport/i/shadow/large-v.png +0 -0
  141. data/frameworks/uki/src/uki-theme/airport/i/shadow/large.png +0 -0
  142. data/frameworks/uki/src/uki-theme/airport/i/slider/bar-m.gif +0 -0
  143. data/frameworks/uki/src/uki-theme/airport/i/slider/bar-m.png +0 -0
  144. data/frameworks/uki/src/uki-theme/airport/i/slider/bar-v.gif +0 -0
  145. data/frameworks/uki/src/uki-theme/airport/i/slider/bar-v.png +0 -0
  146. data/frameworks/uki/src/uki-theme/airport/i/slider/bar.png +0 -0
  147. data/frameworks/uki/src/uki-theme/airport/i/slider/focus.png +0 -0
  148. data/frameworks/uki/src/uki-theme/airport/i/slider/handle.gif +0 -0
  149. data/frameworks/uki/src/uki-theme/airport/i/splitPane/horizontal.gif +0 -0
  150. data/frameworks/uki/src/uki-theme/airport/i/splitPane/horizontal.png +0 -0
  151. data/frameworks/uki/src/uki-theme/airport/i/splitPane/vertical.gif +0 -0
  152. data/frameworks/uki/src/uki-theme/airport/i/x.gif +0 -0
  153. data/frameworks/uki/src/uki-theme/airport.js +322 -0
  154. data/frameworks/uki/src/uki-theme/aristo/i/button/down-c.gif +0 -0
  155. data/frameworks/uki/src/uki-theme/aristo/i/button/down-c.png +0 -0
  156. data/frameworks/uki/src/uki-theme/aristo/i/button/down-h.png +0 -0
  157. data/frameworks/uki/src/uki-theme/aristo/i/button/down-m.png +0 -0
  158. data/frameworks/uki/src/uki-theme/aristo/i/button/down-v.png +0 -0
  159. data/frameworks/uki/src/uki-theme/aristo/i/button/down.png +0 -0
  160. data/frameworks/uki/src/uki-theme/aristo/i/button/focusRing-c.png +0 -0
  161. data/frameworks/uki/src/uki-theme/aristo/i/button/focusRing-h.png +0 -0
  162. data/frameworks/uki/src/uki-theme/aristo/i/button/focusRing-m.png +0 -0
  163. data/frameworks/uki/src/uki-theme/aristo/i/button/focusRing-v.png +0 -0
  164. data/frameworks/uki/src/uki-theme/aristo/i/button/focusRing.png +0 -0
  165. data/frameworks/uki/src/uki-theme/aristo/i/button/normal-c.gif +0 -0
  166. data/frameworks/uki/src/uki-theme/aristo/i/button/normal-c.png +0 -0
  167. data/frameworks/uki/src/uki-theme/aristo/i/button/normal-h.png +0 -0
  168. data/frameworks/uki/src/uki-theme/aristo/i/button/normal-m.png +0 -0
  169. data/frameworks/uki/src/uki-theme/aristo/i/button/normal-v.png +0 -0
  170. data/frameworks/uki/src/uki-theme/aristo/i/button/normal.png +0 -0
  171. data/frameworks/uki/src/uki-theme/aristo/i/checkbox/focus.png +0 -0
  172. data/frameworks/uki/src/uki-theme/aristo/i/checkbox/normal.gif +0 -0
  173. data/frameworks/uki/src/uki-theme/aristo/i/checkbox/normal.png +0 -0
  174. data/frameworks/uki/src/uki-theme/aristo/i/panel/normal-h.png +0 -0
  175. data/frameworks/uki/src/uki-theme/aristo/i/panel/normal-m.png +0 -0
  176. data/frameworks/uki/src/uki-theme/aristo/i/panel/normal.png +0 -0
  177. data/frameworks/uki/src/uki-theme/aristo/i/popup/normal.png +0 -0
  178. data/frameworks/uki/src/uki-theme/aristo/i/radio/focus.png +0 -0
  179. data/frameworks/uki/src/uki-theme/aristo/i/radio/normal.gif +0 -0
  180. data/frameworks/uki/src/uki-theme/aristo/i/radio/normal.png +0 -0
  181. data/frameworks/uki/src/uki-theme/aristo/i/shadow/large-c.png +0 -0
  182. data/frameworks/uki/src/uki-theme/aristo/i/shadow/large-h.png +0 -0
  183. data/frameworks/uki/src/uki-theme/aristo/i/shadow/large-m.png +0 -0
  184. data/frameworks/uki/src/uki-theme/aristo/i/shadow/large-v.png +0 -0
  185. data/frameworks/uki/src/uki-theme/aristo/i/shadow/large.png +0 -0
  186. data/frameworks/uki/src/uki-theme/aristo/i/slider/bar-m.gif +0 -0
  187. data/frameworks/uki/src/uki-theme/aristo/i/slider/bar-m.png +0 -0
  188. data/frameworks/uki/src/uki-theme/aristo/i/slider/bar-v.gif +0 -0
  189. data/frameworks/uki/src/uki-theme/aristo/i/slider/bar-v.png +0 -0
  190. data/frameworks/uki/src/uki-theme/aristo/i/slider/bar.png +0 -0
  191. data/frameworks/uki/src/uki-theme/aristo/i/slider/handle.gif +0 -0
  192. data/frameworks/uki/src/uki-theme/aristo/i/slider/handle.png +0 -0
  193. data/frameworks/uki/src/uki-theme/aristo/i/splitPane/horizontal.gif +0 -0
  194. data/frameworks/uki/src/uki-theme/aristo/i/splitPane/horizontal.png +0 -0
  195. data/frameworks/uki/src/uki-theme/aristo/i/splitPane/vertical.gif +0 -0
  196. data/frameworks/uki/src/uki-theme/aristo/i/x.gif +0 -0
  197. data/frameworks/uki/src/uki-theme/aristo.js +217 -0
  198. data/frameworks/uki/src/uki-view/view/box.js +1 -0
  199. data/frameworks/uki/src/uki-view/view/button.js +126 -0
  200. data/frameworks/uki/src/uki-view/view/checkbox.js +36 -0
  201. data/frameworks/uki/src/uki-view/view/flow.js +48 -0
  202. data/frameworks/uki/src/uki-view/view/image.js +9 -0
  203. data/frameworks/uki/src/uki-view/view/label.js +123 -0
  204. data/frameworks/uki/src/uki-view/view/list/render.js +23 -0
  205. data/frameworks/uki/src/uki-view/view/list.js +442 -0
  206. data/frameworks/uki/src/uki-view/view/popup.js +113 -0
  207. data/frameworks/uki/src/uki-view/view/radio.js +57 -0
  208. data/frameworks/uki/src/uki-view/view/scrollPane.js +139 -0
  209. data/frameworks/uki/src/uki-view/view/slider.js +154 -0
  210. data/frameworks/uki/src/uki-view/view/splitPane.js +213 -0
  211. data/frameworks/uki/src/uki-view/view/table/column.js +96 -0
  212. data/frameworks/uki/src/uki-view/view/table/header.js +53 -0
  213. data/frameworks/uki/src/uki-view/view/table/render.js +25 -0
  214. data/frameworks/uki/src/uki-view/view/table.js +71 -0
  215. data/frameworks/uki/src/uki-view/view/textField.js +145 -0
  216. data/frameworks/uki/src/uki-view/view/toolbar.js +93 -0
  217. data/frameworks/uki/src/uki-view.js +15 -0
  218. data/frameworks/uki/src/uki.js +2 -0
  219. data/frameworks/uki/thin.yaml +11 -0
  220. data/frameworks/uki/uki.rb +38 -0
  221. data/frameworks/uki/uki.ru +2 -0
  222. data/lib/uki/include_js.rb +50 -0
  223. data/lib/uki/project.rb +207 -0
  224. data/lib/uki/routes.rb +20 -0
  225. data/lib/uki/server.rb +42 -0
  226. data/lib/uki.rb +9 -0
  227. data/templates/index.html.erb +10 -0
  228. data/templates/model.js.erb +5 -0
  229. data/templates/myapp.js.erb +44 -0
  230. data/templates/package.js.erb +3 -0
  231. data/templates/spec.html.erb +23 -0
  232. data/templates/spec.js.erb +6 -0
  233. data/templates/view.js.erb +10 -0
  234. data/uki.gemspec +281 -0
  235. metadata +317 -0
@@ -0,0 +1,1773 @@
1
+
2
+ // JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
3
+
4
+ ;(function(){
5
+
6
+ JSpec = {
7
+ version : '3.1.3',
8
+ assert : true,
9
+ cache : {},
10
+ suites : [],
11
+ modules : [],
12
+ allSuites : [],
13
+ matchers : {},
14
+ stubbed : [],
15
+ options : {},
16
+ request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
17
+ stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
18
+
19
+ /**
20
+ * Default context in which bodies are evaluated.
21
+ *
22
+ * Replace context simply by setting JSpec.context
23
+ * to your own like below:
24
+ *
25
+ * JSpec.context = { foo : 'bar' }
26
+ *
27
+ * Contexts can be changed within any body, this can be useful
28
+ * in order to provide specific helper methods to specific suites.
29
+ *
30
+ * To reset (usually in after hook) simply set to null like below:
31
+ *
32
+ * JSpec.context = null
33
+ *
34
+ */
35
+
36
+ defaultContext : {
37
+
38
+ /**
39
+ * Return an object used for proxy assertions.
40
+ * This object is used to indicate that an object
41
+ * should be an instance of _object_, not the constructor
42
+ * itself.
43
+ *
44
+ * @param {function} constructor
45
+ * @return {hash}
46
+ * @api public
47
+ */
48
+
49
+ an_instance_of : function(constructor) {
50
+ return { an_instance_of : constructor }
51
+ },
52
+
53
+ /**
54
+ * Load fixture at _path_.
55
+ *
56
+ * Fixtures are resolved as:
57
+ *
58
+ * - <path>
59
+ * - <path>.html
60
+ *
61
+ * @param {string} path
62
+ * @return {string}
63
+ * @api public
64
+ */
65
+
66
+ fixture : function(path) {
67
+ if (JSpec.cache[path]) return JSpec.cache[path]
68
+ return JSpec.cache[path] =
69
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
70
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
71
+ }
72
+ },
73
+
74
+ // --- Objects
75
+
76
+ reporters : {
77
+
78
+ /**
79
+ * Report to server.
80
+ *
81
+ * Options:
82
+ * - uri specific uri to report to.
83
+ * - verbose weither or not to output messages
84
+ * - failuresOnly output failure messages only
85
+ *
86
+ * @api public
87
+ */
88
+
89
+ Server : function(results, options) {
90
+ var uri = options.uri || 'http://' + window.location.host + '/results'
91
+ JSpec.post(uri, {
92
+ stats: JSpec.stats,
93
+ options: options,
94
+ results: map(results.allSuites, function(suite) {
95
+ if (suite.hasSpecs())
96
+ return {
97
+ description: suite.description,
98
+ specs: map(suite.specs, function(spec) {
99
+ return {
100
+ description: spec.description,
101
+ message: !spec.passed() ? spec.failure().message : null,
102
+ status: spec.requiresImplementation() ? 'pending' :
103
+ spec.passed() ? 'pass' :
104
+ 'fail',
105
+ assertions: map(spec.assertions, function(assertion){
106
+ return {
107
+ passed: assertion.passed
108
+ }
109
+ })
110
+ }
111
+ })
112
+ }
113
+ })
114
+ })
115
+ if ('close' in main) main.close()
116
+ },
117
+
118
+ /**
119
+ * Default reporter, outputting to the DOM.
120
+ *
121
+ * Options:
122
+ * - reportToId id of element to output reports to, defaults to 'jspec'
123
+ * - failuresOnly displays only suites with failing specs
124
+ *
125
+ * @api public
126
+ */
127
+
128
+ DOM : function(results, options) {
129
+ var id = option('reportToId') || 'jspec'
130
+ var report = document.getElementById(id)
131
+ var failuresOnly = option('failuresOnly')
132
+ var classes = results.stats.failures ? 'has-failures' : ''
133
+ if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
134
+
135
+ function bodyContents(body) {
136
+ return JSpec.
137
+ escape(JSpec.contentsOf(body)).
138
+ replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
139
+ replace(/\r\n|\r|\n/gm, '<br/>')
140
+ }
141
+
142
+ report.innerHTML = '<div id="jspec-report" class="' + classes + '"><div class="heading"> \
143
+ <span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
144
+ <span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
145
+ <span class="passes">Duration: <em>' + results.duration + '</em> ms</span> \
146
+ </div><table class="suites">' + map(results.allSuites, function(suite) {
147
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
148
+ if (displaySuite && suite.hasSpecs())
149
+ return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
150
+ map(suite.specs, function(i, spec) {
151
+ return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
152
+ (spec.requiresImplementation() ?
153
+ '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
154
+ (spec.passed() && !failuresOnly) ?
155
+ '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
156
+ !spec.passed() ?
157
+ '<td class="fail">' + escape(spec.description) +
158
+ map(spec.failures(), function(a){ return '<em>' + escape(a.message) + '</em>' }).join('') +
159
+ '</td><td>' + spec.assertionsGraph() + '</td>' :
160
+ '') +
161
+ '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
162
+ }).join('') + '</tr>'
163
+ }).join('') + '</table></div>'
164
+ },
165
+
166
+ /**
167
+ * Terminal reporter.
168
+ *
169
+ * @api public
170
+ */
171
+
172
+ Terminal : function(results, options) {
173
+ failuresOnly = option('failuresOnly')
174
+ print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
175
+ color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
176
+ color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
177
+
178
+ function indent(string) {
179
+ return string.replace(/^(.)/gm, ' $1')
180
+ }
181
+
182
+ each(results.allSuites, function(suite) {
183
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
184
+ if (displaySuite && suite.hasSpecs()) {
185
+ print(color(' ' + suite.description, 'bold'))
186
+ each(suite.specs, function(spec){
187
+ var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
188
+ return graph + color('.', assertion.passed ? 'green' : 'red')
189
+ })
190
+ if (spec.requiresImplementation())
191
+ print(color(' ' + spec.description, 'blue') + assertionsGraph)
192
+ else if (spec.passed() && !failuresOnly)
193
+ print(color(' ' + spec.description, 'green') + assertionsGraph)
194
+ else if (!spec.passed())
195
+ print(color(' ' + spec.description, 'red') + assertionsGraph +
196
+ "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n")
197
+ })
198
+ print("")
199
+ }
200
+ })
201
+
202
+ quit(results.stats.failures)
203
+ },
204
+
205
+ /**
206
+ * Console reporter.
207
+ *
208
+ * @api public
209
+ */
210
+
211
+ Console : function(results, options) {
212
+ console.log('')
213
+ console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
214
+ each(results.allSuites, function(suite) {
215
+ if (suite.ran) {
216
+ console.group(suite.description)
217
+ each(suite.specs, function(spec){
218
+ var assertionCount = spec.assertions.length + ':'
219
+ if (spec.requiresImplementation())
220
+ console.warn(spec.description)
221
+ else if (spec.passed())
222
+ console.log(assertionCount + ' ' + spec.description)
223
+ else
224
+ console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message)
225
+ })
226
+ console.groupEnd()
227
+ }
228
+ })
229
+ }
230
+ },
231
+
232
+ Assertion : function(matcher, actual, expected, negate) {
233
+ extend(this, {
234
+ message: '',
235
+ passed: false,
236
+ actual: actual,
237
+ negate: negate,
238
+ matcher: matcher,
239
+ expected: expected,
240
+
241
+ // Report assertion results
242
+
243
+ report : function() {
244
+ if (JSpec.assert)
245
+ this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
246
+ return this
247
+ },
248
+
249
+ // Run the assertion
250
+
251
+ run : function() {
252
+ // TODO: remove unshifting
253
+ expected.unshift(actual)
254
+ this.result = matcher.match.apply(this, expected)
255
+ this.passed = negate ? !this.result : this.result
256
+ if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name)
257
+ return this
258
+ }
259
+ })
260
+ },
261
+
262
+ ProxyAssertion : function(object, method, times, negate) {
263
+ var self = this
264
+ var old = object[method]
265
+
266
+ // Proxy
267
+
268
+ object[method] = function(){
269
+ args = toArray(arguments)
270
+ result = old.apply(object, args)
271
+ self.calls.push({ args : args, result : result })
272
+ return result
273
+ }
274
+
275
+ // Times
276
+
277
+ this.times = {
278
+ once : 1,
279
+ twice : 2
280
+ }[times] || times || 1
281
+
282
+ extend(this, {
283
+ calls: [],
284
+ message: '',
285
+ defer: true,
286
+ passed: false,
287
+ negate: negate,
288
+ object: object,
289
+ method: method,
290
+
291
+ // Proxy return value
292
+
293
+ and_return : function(result) {
294
+ this.expectedResult = result
295
+ return this
296
+ },
297
+
298
+ // Proxy arguments passed
299
+
300
+ with_args : function() {
301
+ this.expectedArgs = toArray(arguments)
302
+ return this
303
+ },
304
+
305
+ // Check if any calls have failing results
306
+
307
+ anyResultsFail : function() {
308
+ return any(this.calls, function(call){
309
+ return self.expectedResult.an_instance_of ?
310
+ call.result.constructor != self.expectedResult.an_instance_of:
311
+ !equal(self.expectedResult, call.result)
312
+ })
313
+ },
314
+
315
+ // Check if any calls have passing results
316
+
317
+ anyResultsPass : function() {
318
+ return any(this.calls, function(call){
319
+ return self.expectedResult.an_instance_of ?
320
+ call.result.constructor == self.expectedResult.an_instance_of:
321
+ equal(self.expectedResult, call.result)
322
+ })
323
+ },
324
+
325
+ // Return the passing result
326
+
327
+ passingResult : function() {
328
+ return this.anyResultsPass().result
329
+ },
330
+
331
+ // Return the failing result
332
+
333
+ failingResult : function() {
334
+ return this.anyResultsFail().result
335
+ },
336
+
337
+ // Check if any arguments fail
338
+
339
+ anyArgsFail : function() {
340
+ return any(this.calls, function(call){
341
+ return any(self.expectedArgs, function(i, arg){
342
+ if (arg == null) return call.args[i] == null
343
+ return arg.an_instance_of ?
344
+ call.args[i].constructor != arg.an_instance_of:
345
+ !equal(arg, call.args[i])
346
+
347
+ })
348
+ })
349
+ },
350
+
351
+ // Check if any arguments pass
352
+
353
+ anyArgsPass : function() {
354
+ return any(this.calls, function(call){
355
+ return any(self.expectedArgs, function(i, arg){
356
+ return arg.an_instance_of ?
357
+ call.args[i].constructor == arg.an_instance_of:
358
+ equal(arg, call.args[i])
359
+
360
+ })
361
+ })
362
+ },
363
+
364
+ // Return the passing args
365
+
366
+ passingArgs : function() {
367
+ return this.anyArgsPass().args
368
+ },
369
+
370
+ // Return the failing args
371
+
372
+ failingArgs : function() {
373
+ return this.anyArgsFail().args
374
+ },
375
+
376
+ // Report assertion results
377
+
378
+ report : function() {
379
+ if (JSpec.assert)
380
+ this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
381
+ return this
382
+ },
383
+
384
+ // Run the assertion
385
+
386
+ run : function() {
387
+ var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
388
+
389
+ function times(n) {
390
+ return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n]
391
+ }
392
+
393
+ if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
394
+ this.message = methodString + ' to return ' + puts(this.expectedResult) +
395
+ ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult()))
396
+
397
+ if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail()))
398
+ this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) +
399
+ ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs()))
400
+
401
+ if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times)
402
+ this.message = methodString + ' to be called ' + times(this.times) +
403
+ ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
404
+
405
+ if (!this.message.length)
406
+ this.passed = true
407
+
408
+ return this
409
+ }
410
+ })
411
+ },
412
+
413
+ /**
414
+ * Specification Suite block object.
415
+ *
416
+ * @param {string} description
417
+ * @param {function} body
418
+ * @api private
419
+ */
420
+
421
+ Suite : function(description, body) {
422
+ var self = this
423
+ extend(this, {
424
+ body: body,
425
+ description: description,
426
+ suites: [],
427
+ specs: [],
428
+ ran: false,
429
+ hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
430
+
431
+ // Add a spec to the suite
432
+
433
+ addSpec : function(description, body) {
434
+ var spec = new JSpec.Spec(description, body)
435
+ this.specs.push(spec)
436
+ JSpec.stats.specs++ // TODO: abstract
437
+ spec.suite = this
438
+ },
439
+
440
+ // Add a hook to the suite
441
+
442
+ addHook : function(hook, body) {
443
+ this.hooks[hook].push(body)
444
+ },
445
+
446
+ // Add a nested suite
447
+
448
+ addSuite : function(description, body) {
449
+ var suite = new JSpec.Suite(description, body)
450
+ JSpec.allSuites.push(suite)
451
+ suite.name = suite.description
452
+ suite.description = this.description + ' ' + suite.description
453
+ this.suites.push(suite)
454
+ suite.suite = this
455
+ },
456
+
457
+ // Invoke a hook in context to this suite
458
+
459
+ hook : function(hook) {
460
+ if (this.suite) this.suite.hook(hook)
461
+ each(this.hooks[hook], function(body) {
462
+ JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ")
463
+ })
464
+ },
465
+
466
+ // Check if nested suites are present
467
+
468
+ hasSuites : function() {
469
+ return this.suites.length
470
+ },
471
+
472
+ // Check if this suite has specs
473
+
474
+ hasSpecs : function() {
475
+ return this.specs.length
476
+ },
477
+
478
+ // Check if the entire suite passed
479
+
480
+ passed : function() {
481
+ return !any(this.specs, function(spec){
482
+ return !spec.passed()
483
+ })
484
+ }
485
+ })
486
+ },
487
+
488
+ /**
489
+ * Specification block object.
490
+ *
491
+ * @param {string} description
492
+ * @param {function} body
493
+ * @api private
494
+ */
495
+
496
+ Spec : function(description, body) {
497
+ extend(this, {
498
+ body: body,
499
+ description: description,
500
+ assertions: [],
501
+
502
+ // Add passing assertion
503
+
504
+ pass : function(message) {
505
+ this.assertions.push({ passed: true, message: message })
506
+ if (JSpec.assert) ++JSpec.stats.passes
507
+ },
508
+
509
+ // Add failing assertion
510
+
511
+ fail : function(message) {
512
+ this.assertions.push({ passed: false, message: message })
513
+ if (JSpec.assert) ++JSpec.stats.failures
514
+ },
515
+
516
+ // Run deferred assertions
517
+
518
+ runDeferredAssertions : function() {
519
+ each(this.assertions, function(assertion){
520
+ if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion)
521
+ })
522
+ },
523
+
524
+ // Find first failing assertion
525
+
526
+ failure : function() {
527
+ return find(this.assertions, function(assertion){
528
+ return !assertion.passed
529
+ })
530
+ },
531
+
532
+ // Find all failing assertions
533
+
534
+ failures : function() {
535
+ return select(this.assertions, function(assertion){
536
+ return !assertion.passed
537
+ })
538
+ },
539
+
540
+ // Weither or not the spec passed
541
+
542
+ passed : function() {
543
+ return !this.failure()
544
+ },
545
+
546
+ // Weither or not the spec requires implementation (no assertions)
547
+
548
+ requiresImplementation : function() {
549
+ return this.assertions.length == 0
550
+ },
551
+
552
+ // Sprite based assertions graph
553
+
554
+ assertionsGraph : function() {
555
+ return map(this.assertions, function(assertion){
556
+ return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
557
+ }).join('')
558
+ }
559
+ })
560
+ },
561
+
562
+ Module : function(methods) {
563
+ extend(this, methods)
564
+ },
565
+
566
+ JSON : {
567
+
568
+ /**
569
+ * Generic sequences.
570
+ */
571
+
572
+ meta : {
573
+ '\b' : '\\b',
574
+ '\t' : '\\t',
575
+ '\n' : '\\n',
576
+ '\f' : '\\f',
577
+ '\r' : '\\r',
578
+ '"' : '\\"',
579
+ '\\' : '\\\\'
580
+ },
581
+
582
+ /**
583
+ * Escapable sequences.
584
+ */
585
+
586
+ escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
587
+
588
+ /**
589
+ * JSON encode _object_.
590
+ *
591
+ * @param {mixed} object
592
+ * @return {string}
593
+ * @api private
594
+ */
595
+
596
+ encode : function(object) {
597
+ var self = this
598
+ if (object == undefined || object == null) return 'null'
599
+ if (object === true) return 'true'
600
+ if (object === false) return 'false'
601
+ switch (typeof object) {
602
+ case 'number': return object
603
+ case 'string': return this.escapable.test(object) ?
604
+ '"' + object.replace(this.escapable, function (a) {
605
+ return typeof self.meta[a] === 'string' ? self.meta[a] :
606
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
607
+ }) + '"' :
608
+ '"' + object + '"'
609
+ case 'object':
610
+ if (object.constructor == Array)
611
+ return '[' + map(object, function(val){
612
+ return self.encode(val)
613
+ }).join(', ') + ']'
614
+ else if (object)
615
+ return '{' + map(object, function(key, val){
616
+ return self.encode(key) + ':' + self.encode(val)
617
+ }).join(', ') + '}'
618
+ }
619
+ return 'null'
620
+ }
621
+ },
622
+
623
+ // --- DSLs
624
+
625
+ DSLs : {
626
+ snake : {
627
+ expect : function(actual){
628
+ return JSpec.expect(actual)
629
+ },
630
+
631
+ describe : function(description, body) {
632
+ return JSpec.currentSuite.addSuite(description, body)
633
+ },
634
+
635
+ it : function(description, body) {
636
+ return JSpec.currentSuite.addSpec(description, body)
637
+ },
638
+
639
+ before : function(body) {
640
+ return JSpec.currentSuite.addHook('before', body)
641
+ },
642
+
643
+ after : function(body) {
644
+ return JSpec.currentSuite.addHook('after', body)
645
+ },
646
+
647
+ before_each : function(body) {
648
+ return JSpec.currentSuite.addHook('before_each', body)
649
+ },
650
+
651
+ after_each : function(body) {
652
+ return JSpec.currentSuite.addHook('after_each', body)
653
+ },
654
+
655
+ should_behave_like : function(description) {
656
+ return JSpec.shareBehaviorsOf(description)
657
+ }
658
+ }
659
+ },
660
+
661
+ // --- Methods
662
+
663
+ /**
664
+ * Check if _value_ is 'stop'. For use as a
665
+ * utility callback function.
666
+ *
667
+ * @param {mixed} value
668
+ * @return {bool}
669
+ * @api public
670
+ */
671
+
672
+ haveStopped : function(value) {
673
+ return value === 'stop'
674
+ },
675
+
676
+ /**
677
+ * Include _object_ which may be a hash or Module instance.
678
+ *
679
+ * @param {hash, Module} object
680
+ * @return {JSpec}
681
+ * @api public
682
+ */
683
+
684
+ include : function(object) {
685
+ var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object)
686
+ this.modules.push(module)
687
+ if ('init' in module) module.init()
688
+ if ('utilities' in module) extend(this.defaultContext, module.utilities)
689
+ if ('matchers' in module) this.addMatchers(module.matchers)
690
+ if ('reporters' in module) extend(this.reporters, module.reporters)
691
+ if ('DSLs' in module)
692
+ each(module.DSLs, function(name, methods){
693
+ JSpec.DSLs[name] = JSpec.DSLs[name] || {}
694
+ extend(JSpec.DSLs[name], methods)
695
+ })
696
+ return this
697
+ },
698
+
699
+ /**
700
+ * Add a module hook _name_, which is immediately
701
+ * called per module with the _args_ given. An array of
702
+ * hook return values is returned.
703
+ *
704
+ * @param {name} string
705
+ * @param {...} args
706
+ * @return {array}
707
+ * @api private
708
+ */
709
+
710
+ hook : function(name, args) {
711
+ args = toArray(arguments, 1)
712
+ return inject(JSpec.modules, [], function(results, module){
713
+ if (typeof module[name] == 'function')
714
+ results.push(JSpec.evalHook(module, name, args))
715
+ })
716
+ },
717
+
718
+ /**
719
+ * Eval _module_ hook _name_ with _args_. Evaluates in context
720
+ * to the module itself, JSpec, and JSpec.context.
721
+ *
722
+ * @param {Module} module
723
+ * @param {string} name
724
+ * @param {array} args
725
+ * @return {mixed}
726
+ * @api private
727
+ */
728
+
729
+ evalHook : function(module, name, args) {
730
+ hook('evaluatingHookBody', module, name)
731
+ try { return module[name].apply(module, args) }
732
+ catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
733
+ },
734
+
735
+ /**
736
+ * Same as hook() however accepts only one _arg_ which is
737
+ * considered immutable. This function passes the arg
738
+ * to the first module, then passes the return value of the last
739
+ * module called, to the following module.
740
+ *
741
+ * @param {string} name
742
+ * @param {mixed} arg
743
+ * @return {mixed}
744
+ * @api private
745
+ */
746
+
747
+ hookImmutable : function(name, arg) {
748
+ return inject(JSpec.modules, arg, function(result, module){
749
+ if (typeof module[name] == 'function')
750
+ return JSpec.evalHook(module, name, [result])
751
+ })
752
+ },
753
+
754
+ /**
755
+ * Find a suite by its description or name.
756
+ *
757
+ * @param {string} description
758
+ * @return {Suite}
759
+ * @api private
760
+ */
761
+
762
+ findSuite : function(description) {
763
+ return find(this.allSuites, function(suite){
764
+ return suite.name == description || suite.description == description
765
+ })
766
+ },
767
+
768
+ /**
769
+ * Share behaviors (specs) of the given suite with
770
+ * the current suite.
771
+ *
772
+ * @param {string} description
773
+ * @api public
774
+ */
775
+
776
+ shareBehaviorsOf : function(description) {
777
+ if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
778
+ else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name'
779
+ },
780
+
781
+ /**
782
+ * Copy specs from one suite to another.
783
+ *
784
+ * @param {Suite} fromSuite
785
+ * @param {Suite} toSuite
786
+ * @api public
787
+ */
788
+
789
+ copySpecs : function(fromSuite, toSuite) {
790
+ each(fromSuite.specs, function(spec){
791
+ spec.assertions = []
792
+ toSuite.specs.push(spec)
793
+ })
794
+ },
795
+
796
+ /**
797
+ * Convert arguments to an array.
798
+ *
799
+ * @param {object} arguments
800
+ * @param {int} offset
801
+ * @return {array}
802
+ * @api public
803
+ */
804
+
805
+ toArray : function(arguments, offset) {
806
+ return Array.prototype.slice.call(arguments, offset || 0)
807
+ },
808
+
809
+ /**
810
+ * Return ANSI-escaped colored string.
811
+ *
812
+ * @param {string} string
813
+ * @param {string} color
814
+ * @return {string}
815
+ * @api public
816
+ */
817
+
818
+ color : function(string, color) {
819
+ return "\u001B[" + {
820
+ bold : 1,
821
+ black : 30,
822
+ red : 31,
823
+ green : 32,
824
+ yellow : 33,
825
+ blue : 34,
826
+ magenta : 35,
827
+ cyan : 36,
828
+ white : 37
829
+ }[color] + 'm' + string + "\u001B[0m"
830
+ },
831
+
832
+ /**
833
+ * Default matcher message callback.
834
+ *
835
+ * @api private
836
+ */
837
+
838
+ defaultMatcherMessage : function(actual, expected, negate, name) {
839
+ return 'expected ' + puts(actual) + ' to ' +
840
+ (negate ? 'not ' : '') +
841
+ name.replace(/_/g, ' ') +
842
+ ' ' + (expected.length > 1 ?
843
+ puts.apply(this, expected.slice(1)) :
844
+ '')
845
+ },
846
+
847
+ /**
848
+ * Normalize a matcher message.
849
+ *
850
+ * When no messge callback is present the defaultMatcherMessage
851
+ * will be assigned, will suffice for most matchers.
852
+ *
853
+ * @param {hash} matcher
854
+ * @return {hash}
855
+ * @api public
856
+ */
857
+
858
+ normalizeMatcherMessage : function(matcher) {
859
+ if (typeof matcher.message != 'function')
860
+ matcher.message = this.defaultMatcherMessage
861
+ return matcher
862
+ },
863
+
864
+ /**
865
+ * Normalize a matcher body
866
+ *
867
+ * This process allows the following conversions until
868
+ * the matcher is in its final normalized hash state.
869
+ *
870
+ * - '==' becomes 'actual == expected'
871
+ * - 'actual == expected' becomes 'return actual == expected'
872
+ * - function(actual, expected) { return actual == expected } becomes
873
+ * { match : function(actual, expected) { return actual == expected }}
874
+ *
875
+ * @param {mixed} body
876
+ * @return {hash}
877
+ * @api public
878
+ */
879
+
880
+ normalizeMatcherBody : function(body) {
881
+ switch (body.constructor) {
882
+ case String:
883
+ if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
884
+ if (body.length < 4) body = 'actual ' + body + ' expected'
885
+ return { match: function(actual, expected) { return eval(body) }}
886
+
887
+ case Function:
888
+ return { match: body }
889
+
890
+ default:
891
+ return body
892
+ }
893
+ },
894
+
895
+ /**
896
+ * Get option value. This method first checks if
897
+ * the option key has been set via the query string,
898
+ * otherwise returning the options hash value.
899
+ *
900
+ * @param {string} key
901
+ * @return {mixed}
902
+ * @api public
903
+ */
904
+
905
+ option : function(key) {
906
+ return (value = query(key)) !== null ? value :
907
+ JSpec.options[key] || null
908
+ },
909
+
910
+ /**
911
+ * Check if object _a_, is equal to object _b_.
912
+ *
913
+ * @param {object} a
914
+ * @param {object} b
915
+ * @return {bool}
916
+ * @api private
917
+ */
918
+
919
+ equal: function(a, b) {
920
+ if (typeof a != typeof b) return
921
+ if (a === b) return true
922
+ if (a instanceof RegExp)
923
+ return a.toString() === b.toString()
924
+ if (a instanceof Date)
925
+ return Number(a) === Number(b)
926
+ if (typeof a != 'object') return
927
+ if (a.length !== undefined)
928
+ if (a.length !== b.length) return
929
+ else
930
+ for (var i = 0, len = a.length; i < len; ++i)
931
+ if (!equal(a[i], b[i]))
932
+ return
933
+ for (var key in a)
934
+ if (!equal(a[key], b[key]))
935
+ return
936
+ return true
937
+ },
938
+
939
+ /**
940
+ * Return last element of an array.
941
+ *
942
+ * @param {array} array
943
+ * @return {object}
944
+ * @api public
945
+ */
946
+
947
+ last : function(array) {
948
+ return array[array.length - 1]
949
+ },
950
+
951
+ /**
952
+ * Convert object(s) to a print-friend string.
953
+ *
954
+ * @param {...} object
955
+ * @return {string}
956
+ * @api public
957
+ */
958
+
959
+ puts : function(object) {
960
+ if (arguments.length > 1)
961
+ return map(toArray(arguments), function(arg){
962
+ return puts(arg)
963
+ }).join(', ')
964
+ if (object === undefined) return 'undefined'
965
+ if (object === null) return 'null'
966
+ if (object === true) return 'true'
967
+ if (object === false) return 'false'
968
+ if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
969
+ if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
970
+ if (object.jquery) return object.get(0).outerHTML
971
+ if (object.nodeName) return object.outerHTML
972
+ switch (object.constructor) {
973
+ case Function: return object.name || object
974
+ case String:
975
+ return '"' + object
976
+ .replace(/"/g, '\\"')
977
+ .replace(/\n/g, '\\n')
978
+ .replace(/\t/g, '\\t')
979
+ + '"'
980
+ case Array:
981
+ return inject(object, '[', function(b, v){
982
+ return b + ', ' + puts(v)
983
+ }).replace('[,', '[') + ' ]'
984
+ case Object:
985
+ object.__hit__ = true
986
+ return inject(object, '{', function(b, k, v) {
987
+ if (k == '__hit__') return b
988
+ return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
989
+ }).replace('{,', '{') + ' }'
990
+ default:
991
+ return object.toString()
992
+ }
993
+ },
994
+
995
+ /**
996
+ * Escape HTML.
997
+ *
998
+ * @param {string} html
999
+ * @return {string}
1000
+ * @api public
1001
+ */
1002
+
1003
+ escape : function(html) {
1004
+ return html.toString()
1005
+ .replace(/&/gmi, '&amp;')
1006
+ .replace(/"/gmi, '&quot;')
1007
+ .replace(/>/gmi, '&gt;')
1008
+ .replace(/</gmi, '&lt;')
1009
+ },
1010
+
1011
+ /**
1012
+ * Perform an assertion without reporting.
1013
+ *
1014
+ * This method is primarily used for internal
1015
+ * matchers in order retain DRYness. May be invoked
1016
+ * like below:
1017
+ *
1018
+ * does('foo', 'eql', 'foo')
1019
+ * does([1,2], 'include', 1, 2)
1020
+ *
1021
+ * External hooks are not run for internal assertions
1022
+ * performed by does().
1023
+ *
1024
+ * @param {mixed} actual
1025
+ * @param {string} matcher
1026
+ * @param {...} expected
1027
+ * @return {mixed}
1028
+ * @api private
1029
+ */
1030
+
1031
+ does : function(actual, matcher, expected) {
1032
+ var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
1033
+ return assertion.run().result
1034
+ },
1035
+
1036
+ /**
1037
+ * Perform an assertion.
1038
+ *
1039
+ * expect(true).to('be', true)
1040
+ * expect('foo').not_to('include', 'bar')
1041
+ * expect([1, [2]]).to('include', 1, [2])
1042
+ *
1043
+ * @param {mixed} actual
1044
+ * @return {hash}
1045
+ * @api public
1046
+ */
1047
+
1048
+ expect : function(actual) {
1049
+ function assert(matcher, args, negate) {
1050
+ var expected = toArray(args, 1)
1051
+ matcher.negate = negate
1052
+ assertion = new JSpec.Assertion(matcher, actual, expected, negate)
1053
+ hook('beforeAssertion', assertion)
1054
+ if (matcher.defer) assertion.run()
1055
+ else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
1056
+ return assertion.result
1057
+ }
1058
+
1059
+ function to(matcher) {
1060
+ return assert(matcher, arguments, false)
1061
+ }
1062
+
1063
+ function not_to(matcher) {
1064
+ return assert(matcher, arguments, true)
1065
+ }
1066
+
1067
+ return {
1068
+ to : to,
1069
+ should : to,
1070
+ not_to: not_to,
1071
+ should_not : not_to
1072
+ }
1073
+ },
1074
+
1075
+ /**
1076
+ * Strim whitespace or chars.
1077
+ *
1078
+ * @param {string} string
1079
+ * @param {string} chars
1080
+ * @return {string}
1081
+ * @api public
1082
+ */
1083
+
1084
+ strip : function(string, chars) {
1085
+ return string.
1086
+ replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
1087
+ replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
1088
+ },
1089
+
1090
+ /**
1091
+ * Call an iterator callback with arguments a, or b
1092
+ * depending on the arity of the callback.
1093
+ *
1094
+ * @param {function} callback
1095
+ * @param {mixed} a
1096
+ * @param {mixed} b
1097
+ * @return {mixed}
1098
+ * @api private
1099
+ */
1100
+
1101
+ callIterator : function(callback, a, b) {
1102
+ return callback.length == 1 ? callback(b) : callback(a, b)
1103
+ },
1104
+
1105
+ /**
1106
+ * Extend an object with another.
1107
+ *
1108
+ * @param {object} object
1109
+ * @param {object} other
1110
+ * @api public
1111
+ */
1112
+
1113
+ extend : function(object, other) {
1114
+ each(other, function(property, value){
1115
+ object[property] = value
1116
+ })
1117
+ },
1118
+
1119
+ /**
1120
+ * Iterate an object, invoking the given callback.
1121
+ *
1122
+ * @param {hash, array} object
1123
+ * @param {function} callback
1124
+ * @return {JSpec}
1125
+ * @api public
1126
+ */
1127
+
1128
+ each : function(object, callback) {
1129
+ if (object.constructor == Array)
1130
+ for (var i = 0, len = object.length; i < len; ++i)
1131
+ callIterator(callback, i, object[i])
1132
+ else
1133
+ for (var key in object)
1134
+ if (object.hasOwnProperty(key))
1135
+ callIterator(callback, key, object[key])
1136
+ },
1137
+
1138
+ /**
1139
+ * Iterate with memo.
1140
+ *
1141
+ * @param {hash, array} object
1142
+ * @param {object} memo
1143
+ * @param {function} callback
1144
+ * @return {object}
1145
+ * @api public
1146
+ */
1147
+
1148
+ inject : function(object, memo, callback) {
1149
+ each(object, function(key, value){
1150
+ memo = (callback.length == 2 ?
1151
+ callback(memo, value):
1152
+ callback(memo, key, value)) ||
1153
+ memo
1154
+ })
1155
+ return memo
1156
+ },
1157
+
1158
+ /**
1159
+ * Destub _object_'s _method_. When no _method_ is passed
1160
+ * all stubbed methods are destubbed. When no arguments
1161
+ * are passed every object found in JSpec.stubbed will be
1162
+ * destubbed.
1163
+ *
1164
+ * @param {mixed} object
1165
+ * @param {string} method
1166
+ * @api public
1167
+ */
1168
+
1169
+ destub : function(object, method) {
1170
+ if (method) {
1171
+ if (object['__prototype__' + method])
1172
+ delete object[method]
1173
+ else
1174
+ object[method] = object['__original__' + method]
1175
+ delete object['__prototype__' + method]
1176
+ delete object['__original____' + method]
1177
+ }
1178
+ else if (object) {
1179
+ for (var key in object)
1180
+ if (captures = key.match(/^(?:__prototype__|__original__)(.*)/))
1181
+ destub(object, captures[1])
1182
+ }
1183
+ else
1184
+ while (JSpec.stubbed.length)
1185
+ destub(JSpec.stubbed.shift())
1186
+ },
1187
+
1188
+ /**
1189
+ * Stub _object_'s _method_.
1190
+ *
1191
+ * stub(foo, 'toString').and_return('bar')
1192
+ *
1193
+ * @param {mixed} object
1194
+ * @param {string} method
1195
+ * @return {hash}
1196
+ * @api public
1197
+ */
1198
+
1199
+ stub : function(object, method) {
1200
+ hook('stubbing', object, method)
1201
+ JSpec.stubbed.push(object)
1202
+ var type = object.hasOwnProperty(method) ? '__original__' : '__prototype__'
1203
+ object[type + method] = object[method]
1204
+ object[method] = function(){}
1205
+ return {
1206
+ and_return : function(value) {
1207
+ if (typeof value == 'function') object[method] = value
1208
+ else object[method] = function(){ return value }
1209
+ }
1210
+ }
1211
+ },
1212
+
1213
+ /**
1214
+ * Map callback return values.
1215
+ *
1216
+ * @param {hash, array} object
1217
+ * @param {function} callback
1218
+ * @return {array}
1219
+ * @api public
1220
+ */
1221
+
1222
+ map : function(object, callback) {
1223
+ return inject(object, [], function(memo, key, value){
1224
+ memo.push(callIterator(callback, key, value))
1225
+ })
1226
+ },
1227
+
1228
+ /**
1229
+ * Returns the first matching expression or null.
1230
+ *
1231
+ * @param {hash, array} object
1232
+ * @param {function} callback
1233
+ * @return {mixed}
1234
+ * @api public
1235
+ */
1236
+
1237
+ any : function(object, callback) {
1238
+ return inject(object, null, function(state, key, value){
1239
+ if (state == undefined)
1240
+ return callIterator(callback, key, value) ? value : state
1241
+ })
1242
+ },
1243
+
1244
+ /**
1245
+ * Returns an array of values collected when the callback
1246
+ * given evaluates to true.
1247
+ *
1248
+ * @param {hash, array} object
1249
+ * @return {function} callback
1250
+ * @return {array}
1251
+ * @api public
1252
+ */
1253
+
1254
+ select : function(object, callback) {
1255
+ return inject(object, [], function(selected, key, value){
1256
+ if (callIterator(callback, key, value))
1257
+ selected.push(value)
1258
+ })
1259
+ },
1260
+
1261
+ /**
1262
+ * Define matchers.
1263
+ *
1264
+ * @param {hash} matchers
1265
+ * @api public
1266
+ */
1267
+
1268
+ addMatchers : function(matchers) {
1269
+ each(matchers, function(name, body){
1270
+ JSpec.addMatcher(name, body)
1271
+ })
1272
+ },
1273
+
1274
+ /**
1275
+ * Define a matcher.
1276
+ *
1277
+ * @param {string} name
1278
+ * @param {hash, function, string} body
1279
+ * @api public
1280
+ */
1281
+
1282
+ addMatcher : function(name, body) {
1283
+ hook('addingMatcher', name, body)
1284
+ if (name.indexOf(' ') != -1) {
1285
+ var matchers = name.split(/\s+/)
1286
+ var prefix = matchers.shift()
1287
+ each(matchers, function(name) {
1288
+ JSpec.addMatcher(prefix + '_' + name, body(name))
1289
+ })
1290
+ }
1291
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
1292
+ this.matchers[name].name = name
1293
+ },
1294
+
1295
+ /**
1296
+ * Add a root suite to JSpec.
1297
+ *
1298
+ * @param {string} description
1299
+ * @param {body} function
1300
+ * @api public
1301
+ */
1302
+
1303
+ describe : function(description, body) {
1304
+ var suite = new JSpec.Suite(description, body)
1305
+ hook('addingSuite', suite)
1306
+ this.allSuites.push(suite)
1307
+ this.suites.push(suite)
1308
+ },
1309
+
1310
+ /**
1311
+ * Return the contents of a function body.
1312
+ *
1313
+ * @param {function} body
1314
+ * @return {string}
1315
+ * @api public
1316
+ */
1317
+
1318
+ contentsOf : function(body) {
1319
+ return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
1320
+ },
1321
+
1322
+ /**
1323
+ * Evaluate a JSpec capture body.
1324
+ *
1325
+ * @param {function} body
1326
+ * @param {string} errorMessage (optional)
1327
+ * @return {Type}
1328
+ * @api private
1329
+ */
1330
+
1331
+ evalBody : function(body, errorMessage) {
1332
+ var dsl = this.DSL || this.DSLs.snake
1333
+ var matchers = this.matchers
1334
+ var context = this.context || this.defaultContext
1335
+ var contents = this.contentsOf(body)
1336
+ hook('evaluatingBody', dsl, matchers, context, contents)
1337
+ try { with (dsl){ with (context) { with (matchers) { eval(contents) }}} }
1338
+ catch(e) { error(errorMessage, e) }
1339
+ },
1340
+
1341
+ /**
1342
+ * Pre-process a string of JSpec.
1343
+ *
1344
+ * @param {string} input
1345
+ * @return {string}
1346
+ * @api private
1347
+ */
1348
+
1349
+ preprocess : function(input) {
1350
+ if (typeof input != 'string') return
1351
+ input = hookImmutable('preprocessing', input)
1352
+ return input.
1353
+ replace(/\t/g, ' ').
1354
+ replace(/\r\n|\n|\r/g, '\n').
1355
+ split('__END__')[0].
1356
+ replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
1357
+ replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
1358
+ replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
1359
+ replace(/^ *(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
1360
+ replace(/^\s*end(?=\s|$)/gm, '});').
1361
+ replace(/-\{/g, 'function(){').
1362
+ replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
1363
+ replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
1364
+ replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
1365
+ replace(/, \)/g, ')').
1366
+ replace(/should\.not/g, 'should_not')
1367
+ },
1368
+
1369
+ /**
1370
+ * Create a range string which can be evaluated to a native array.
1371
+ *
1372
+ * @param {int} start
1373
+ * @param {int} end
1374
+ * @return {string}
1375
+ * @api public
1376
+ */
1377
+
1378
+ range : function(start, end) {
1379
+ var current = parseInt(start), end = parseInt(end), values = [current]
1380
+ if (end > current) while (++current <= end) values.push(current)
1381
+ else while (--current >= end) values.push(current)
1382
+ return '[' + values + ']'
1383
+ },
1384
+
1385
+ /**
1386
+ * Report on the results.
1387
+ *
1388
+ * @api public
1389
+ */
1390
+
1391
+ report : function() {
1392
+ this.duration = Number(new Date) - this.start
1393
+ hook('reporting', JSpec.options)
1394
+ new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
1395
+ },
1396
+
1397
+ /**
1398
+ * Run the spec suites. Options are merged
1399
+ * with JSpec options when present.
1400
+ *
1401
+ * @param {hash} options
1402
+ * @return {JSpec}
1403
+ * @api public
1404
+ */
1405
+
1406
+ run : function(options) {
1407
+ if (any(hook('running'), haveStopped)) return this
1408
+ if (options) extend(this.options, options)
1409
+ this.start = Number(new Date)
1410
+ each(this.suites, function(suite) { JSpec.runSuite(suite) })
1411
+ return this
1412
+ },
1413
+
1414
+ /**
1415
+ * Run a suite.
1416
+ *
1417
+ * @param {Suite} suite
1418
+ * @api public
1419
+ */
1420
+
1421
+ runSuite : function(suite) {
1422
+ this.currentSuite = suite
1423
+ this.evalBody(suite.body)
1424
+ suite.ran = true
1425
+ hook('beforeSuite', suite), suite.hook('before')
1426
+ each(suite.specs, function(spec) {
1427
+ hook('beforeSpec', spec)
1428
+ suite.hook('before_each')
1429
+ JSpec.runSpec(spec)
1430
+ hook('afterSpec', spec)
1431
+ suite.hook('after_each')
1432
+ })
1433
+ if (suite.hasSuites()) {
1434
+ each(suite.suites, function(suite) {
1435
+ JSpec.runSuite(suite)
1436
+ })
1437
+ }
1438
+ hook('afterSuite', suite), suite.hook('after')
1439
+ this.stats.suitesFinished++
1440
+ },
1441
+
1442
+ /**
1443
+ * Report a failure for the current spec.
1444
+ *
1445
+ * @param {string} message
1446
+ * @api public
1447
+ */
1448
+
1449
+ fail : function(message) {
1450
+ JSpec.currentSpec.fail(message)
1451
+ },
1452
+
1453
+ /**
1454
+ * Report a passing assertion for the current spec.
1455
+ *
1456
+ * @param {string} message
1457
+ * @api public
1458
+ */
1459
+
1460
+ pass : function(message) {
1461
+ JSpec.currentSpec.pass(message)
1462
+ },
1463
+
1464
+ /**
1465
+ * Run a spec.
1466
+ *
1467
+ * @param {Spec} spec
1468
+ * @api public
1469
+ */
1470
+
1471
+ runSpec : function(spec) {
1472
+ this.currentSpec = spec
1473
+ try { this.evalBody(spec.body) }
1474
+ catch (e) { fail(e) }
1475
+ spec.runDeferredAssertions()
1476
+ destub()
1477
+ this.stats.specsFinished++
1478
+ this.stats.assertions += spec.assertions.length
1479
+ },
1480
+
1481
+ /**
1482
+ * Require a dependency, with optional message.
1483
+ *
1484
+ * @param {string} dependency
1485
+ * @param {string} message (optional)
1486
+ * @return {JSpec}
1487
+ * @api public
1488
+ */
1489
+
1490
+ requires : function(dependency, message) {
1491
+ hook('requiring', dependency, message)
1492
+ try { eval(dependency) }
1493
+ catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message }
1494
+ return this
1495
+ },
1496
+
1497
+ /**
1498
+ * Query against the current query strings keys
1499
+ * or the queryString specified.
1500
+ *
1501
+ * @param {string} key
1502
+ * @param {string} queryString
1503
+ * @return {string, null}
1504
+ * @api private
1505
+ */
1506
+
1507
+ query : function(key, queryString) {
1508
+ var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
1509
+ return inject(queryString.split('&'), null, function(value, pair){
1510
+ parts = pair.split('=')
1511
+ return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
1512
+ })
1513
+ },
1514
+
1515
+ /**
1516
+ * Throw a JSpec related error.
1517
+ *
1518
+ * @param {string} message
1519
+ * @param {Exception} e
1520
+ * @api public
1521
+ */
1522
+
1523
+ error : function(message, e) {
1524
+ throw (message ? message : '') + e.toString() +
1525
+ (e.line ? ' near line ' + e.line : '')
1526
+ },
1527
+
1528
+ /**
1529
+ * Ad-hoc POST request for JSpec server usage.
1530
+ *
1531
+ * @param {string} uri
1532
+ * @param {string} data
1533
+ * @api private
1534
+ */
1535
+
1536
+ post : function(uri, data) {
1537
+ if (any(hook('posting', uri, data), haveStopped)) return
1538
+ var request = this.xhr()
1539
+ request.open('POST', uri, false)
1540
+ request.setRequestHeader('Content-Type', 'application/json')
1541
+ request.send(JSpec.JSON.encode(data))
1542
+ },
1543
+
1544
+ /**
1545
+ * Instantiate an XMLHttpRequest.
1546
+ *
1547
+ * Here we utilize IE's lame ActiveXObjects first which
1548
+ * allow IE access serve files via the file: protocol, otherwise
1549
+ * we then default to XMLHttpRequest.
1550
+ *
1551
+ * @return {XMLHttpRequest, ActiveXObject}
1552
+ * @api private
1553
+ */
1554
+
1555
+ xhr : function() {
1556
+ return this.ieXhr() || new JSpec.request
1557
+ },
1558
+
1559
+ /**
1560
+ * Return Microsoft piece of crap ActiveXObject.
1561
+ *
1562
+ * @return {ActiveXObject}
1563
+ * @api public
1564
+ */
1565
+
1566
+ ieXhr : function() {
1567
+ function object(str) {
1568
+ try { return new ActiveXObject(str) } catch(e) {}
1569
+ }
1570
+ return object('Msxml2.XMLHTTP.6.0') ||
1571
+ object('Msxml2.XMLHTTP.3.0') ||
1572
+ object('Msxml2.XMLHTTP') ||
1573
+ object('Microsoft.XMLHTTP')
1574
+ },
1575
+
1576
+ /**
1577
+ * Check for HTTP request support.
1578
+ *
1579
+ * @return {bool}
1580
+ * @api private
1581
+ */
1582
+
1583
+ hasXhr : function() {
1584
+ return JSpec.request || 'ActiveXObject' in main
1585
+ },
1586
+
1587
+ /**
1588
+ * Try loading _file_ returning the contents
1589
+ * string or null. Chain to locate / read a file.
1590
+ *
1591
+ * @param {string} file
1592
+ * @return {string}
1593
+ * @api public
1594
+ */
1595
+
1596
+ tryLoading : function(file) {
1597
+ try { return JSpec.load(file) } catch (e) {}
1598
+ },
1599
+
1600
+ /**
1601
+ * Load a _file_'s contents.
1602
+ *
1603
+ * @param {string} file
1604
+ * @param {function} callback
1605
+ * @return {string}
1606
+ * @api public
1607
+ */
1608
+
1609
+ load : function(file, callback) {
1610
+ if (any(hook('loading', file), haveStopped)) return
1611
+ if ('readFile' in main)
1612
+ return readFile(file)
1613
+ else if (this.hasXhr()) {
1614
+ var request = this.xhr()
1615
+ request.open('GET', file, false)
1616
+ request.send(null)
1617
+ if (request.readyState == 4 &&
1618
+ (request.status == 0 ||
1619
+ request.status.toString().charAt(0) == 2))
1620
+ return request.responseText
1621
+ }
1622
+ else
1623
+ error("failed to load `" + file + "'")
1624
+ },
1625
+
1626
+ /**
1627
+ * Load, pre-process, and evaluate a file.
1628
+ *
1629
+ * @param {string} file
1630
+ * @param {JSpec}
1631
+ * @api public
1632
+ */
1633
+
1634
+ exec : function(file) {
1635
+ if (any(hook('executing', file), haveStopped)) return this
1636
+ eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
1637
+ return this
1638
+ }
1639
+ }
1640
+
1641
+ // --- Utility functions
1642
+
1643
+ var main = this
1644
+ var find = JSpec.any
1645
+ var utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
1646
+ error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
1647
+ while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
1648
+ if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
1649
+
1650
+ // --- Matchers
1651
+
1652
+ addMatchers({
1653
+ equal : "===",
1654
+ eql : "equal(actual, expected)",
1655
+ be : "alias equal",
1656
+ be_greater_than : ">",
1657
+ be_less_than : "<",
1658
+ be_at_least : ">=",
1659
+ be_at_most : "<=",
1660
+ be_a : "actual.constructor == expected",
1661
+ be_an : "alias be_a",
1662
+ be_an_instance_of : "actual instanceof expected",
1663
+ be_null : "actual == null",
1664
+ be_true : "actual == true",
1665
+ be_false : "actual == false",
1666
+ be_undefined : "typeof actual == 'undefined'",
1667
+ be_type : "typeof actual == expected",
1668
+ match : "typeof actual == 'string' ? actual.match(expected) : false",
1669
+ respond_to : "typeof actual[expected] == 'function'",
1670
+ have_length : "actual.length == expected",
1671
+ be_within : "actual >= expected[0] && actual <= last(expected)",
1672
+ have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
1673
+
1674
+ receive : { defer : true, match : function(actual, method, times) {
1675
+ proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
1676
+ JSpec.currentSpec.assertions.push(proxy)
1677
+ return proxy
1678
+ }},
1679
+
1680
+ be_empty : function(actual) {
1681
+ if (actual.constructor == Object && actual.length == undefined)
1682
+ for (var key in actual)
1683
+ return false;
1684
+ return !actual.length
1685
+ },
1686
+
1687
+ include : function(actual) {
1688
+ for (state = true, i = 1; i < arguments.length; i++) {
1689
+ arg = arguments[i]
1690
+ switch (actual.constructor) {
1691
+ case String:
1692
+ case Number:
1693
+ case RegExp:
1694
+ case Function:
1695
+ state = actual.toString().indexOf(arg) !== -1
1696
+ break
1697
+
1698
+ case Object:
1699
+ state = arg in actual
1700
+ break
1701
+
1702
+ case Array:
1703
+ state = any(actual, function(value){ return equal(value, arg) })
1704
+ break
1705
+ }
1706
+ if (!state) return false
1707
+ }
1708
+ return true
1709
+ },
1710
+
1711
+ throw_error : { match : function(actual, expected, message) {
1712
+ try { actual() }
1713
+ catch (e) {
1714
+ this.e = e
1715
+ var assert = function(arg) {
1716
+ switch (arg.constructor) {
1717
+ case RegExp : return arg.test(e.message || e.toString())
1718
+ case String : return arg == (e.message || e.toString())
1719
+ case Function : return e instanceof arg || e.name == arg.name
1720
+ }
1721
+ }
1722
+ return message ? assert(expected) && assert(message) :
1723
+ expected ? assert(expected) :
1724
+ true
1725
+ }
1726
+ }, message : function(actual, expected, negate) {
1727
+ // TODO: refactor when actual is not in expected [0]
1728
+ var message_for = function(i) {
1729
+ if (expected[i] == undefined) return 'exception'
1730
+ switch (expected[i].constructor) {
1731
+ case RegExp : return 'exception matching ' + puts(expected[i])
1732
+ case String : return 'exception of ' + puts(expected[i])
1733
+ case Function : return expected[i].name || 'Error'
1734
+ }
1735
+ }
1736
+ exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
1737
+ return 'expected ' + exception + (negate ? ' not ' : '' ) +
1738
+ ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
1739
+ }},
1740
+
1741
+ have : function(actual, length, property) {
1742
+ return actual[property].length == length
1743
+ },
1744
+
1745
+ have_at_least : function(actual, length, property) {
1746
+ return actual[property].length >= length
1747
+ },
1748
+
1749
+ have_at_most :function(actual, length, property) {
1750
+ return actual[property].length <= length
1751
+ },
1752
+
1753
+ have_within : function(actual, range, property) {
1754
+ length = actual[property].length
1755
+ return length >= range.shift() && length <= range.pop()
1756
+ },
1757
+
1758
+ have_prop : function(actual, property, value) {
1759
+ return actual[property] == null ||
1760
+ actual[property] instanceof Function ? false:
1761
+ value == null ? true:
1762
+ does(actual[property], 'eql', value)
1763
+ },
1764
+
1765
+ have_property : function(actual, property, value) {
1766
+ return actual[property] == null ||
1767
+ actual[property] instanceof Function ? false:
1768
+ value == null ? true:
1769
+ value === actual[property]
1770
+ }
1771
+ })
1772
+
1773
+ })()