unageanu-jiji 1.1.4 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. data/ChangeLog +24 -1
  2. data/README +0 -0
  3. data/base/shared_lib/moving_average.rb +34 -33
  4. data/base/shared_lib/system/cross.rb +76 -0
  5. data/base/shared_lib/system/position_manager.rb +160 -0
  6. data/base/shared_lib/system/signal.rb +347 -0
  7. data/html/css/datatable.css +4 -4
  8. data/html/css/default.css +57 -5
  9. data/html/css/treeview.css +205 -0
  10. data/html/img/bin_closed.png +0 -0
  11. data/html/img/bin_empty.png +0 -0
  12. data/html/img/button_add_small.gif +0 -0
  13. data/html/img/button_add_small_gray.gif +0 -0
  14. data/html/img/button_add_small_over.gif +0 -0
  15. data/html/img/button_mkcol.gif +0 -0
  16. data/html/img/button_mkcol_gray.gif +0 -0
  17. data/html/img/button_mkcol_over.gif +0 -0
  18. data/html/img/button_remove_small.gif +0 -0
  19. data/html/img/button_remove_small_gray.gif +0 -0
  20. data/html/img/button_remove_small_over.gif +0 -0
  21. data/html/img/button_rename.gif +0 -0
  22. data/html/img/button_rename_gray.gif +0 -0
  23. data/html/img/button_rename_over.gif +0 -0
  24. data/html/img/control_play.png +0 -0
  25. data/html/img/control_play_blue.png +0 -0
  26. data/html/img/folder.png +0 -0
  27. data/html/img/folder_brick.png +0 -0
  28. data/html/img/folder_user.png +0 -0
  29. data/html/{js/codepress/images → img}/line-numbers.png +0 -0
  30. data/html/img/page_white_ruby.png +0 -0
  31. data/html/img/sidebar_agent_edit.png +0 -0
  32. data/html/img/sidebar_agent_edit_over.png +0 -0
  33. data/html/img/sidebar_agent_edit_s.png +0 -0
  34. data/html/img/yui/treeview-sprite.gif +0 -0
  35. data/html/index.html +34 -18
  36. data/html/js/agent-editor/agent-editor-page.js +324 -0
  37. data/html/js/agent-editor/agent-editor.js +363 -0
  38. data/html/js/agent-editor/agent-list-tree.js +251 -0
  39. data/html/js/agent-selector.js +23 -28
  40. data/html/js/app.js +63 -91
  41. data/html/js/bt-create-page.js +25 -19
  42. data/html/js/container-min.js +1 -1
  43. data/html/js/container.js +944 -0
  44. data/html/js/edit_area/autocompletion.js +11 -13
  45. data/html/js/edit_area/edit_area.css +79 -40
  46. data/html/js/edit_area/edit_area.js +255 -226
  47. data/html/js/edit_area/edit_area_compressor.php +4 -4
  48. data/html/js/edit_area/edit_area_full.gz +0 -0
  49. data/html/js/edit_area/edit_area_full.js +31 -31
  50. data/html/js/edit_area/edit_area_full_with_plugins.gz +0 -0
  51. data/html/js/edit_area/edit_area_full_with_plugins.js +31 -31
  52. data/html/js/edit_area/edit_area_functions.js +448 -341
  53. data/html/js/edit_area/edit_area_loader.js +409 -397
  54. data/html/js/edit_area/elements_functions.js +120 -123
  55. data/html/js/edit_area/highlight.js +305 -197
  56. data/html/js/edit_area/images/goto.png +0 -0
  57. data/html/js/edit_area/images/help.png +0 -0
  58. data/html/js/edit_area/images/redo.png +0 -0
  59. data/html/js/edit_area/images/save.png +0 -0
  60. data/html/js/edit_area/images/search.png +0 -0
  61. data/html/js/edit_area/images/undo.png +0 -0
  62. data/html/js/edit_area/images/word_wrap.gif +0 -0
  63. data/html/js/edit_area/keyboard.js +5 -5
  64. data/html/js/edit_area/langs/bg.js +73 -0
  65. data/html/js/edit_area/langs/cs.js +2 -0
  66. data/html/js/edit_area/langs/de.js +2 -0
  67. data/html/js/edit_area/langs/dk.js +2 -0
  68. data/html/js/edit_area/langs/en.js +2 -0
  69. data/html/js/edit_area/langs/eo.js +2 -0
  70. data/html/js/edit_area/langs/es.js +2 -0
  71. data/html/js/edit_area/langs/fi.js +67 -0
  72. data/html/js/edit_area/langs/fr.js +2 -0
  73. data/html/js/edit_area/langs/hr.js +2 -0
  74. data/html/js/edit_area/langs/it.js +2 -0
  75. data/html/js/edit_area/langs/ja.js +2 -0
  76. data/html/js/edit_area/langs/mk.js +2 -0
  77. data/html/js/edit_area/langs/nl.js +2 -0
  78. data/html/js/edit_area/langs/pl.js +2 -0
  79. data/html/js/edit_area/langs/pt.js +2 -0
  80. data/html/js/edit_area/langs/ru.js +2 -0
  81. data/html/js/edit_area/langs/sk.js +2 -0
  82. data/html/js/edit_area/langs/zh.js +67 -0
  83. data/html/js/edit_area/manage_area.js +362 -205
  84. data/html/js/edit_area/plugins/charmap/langs/bg.js +12 -0
  85. data/html/js/edit_area/plugins/charmap/langs/zh.js +6 -0
  86. data/html/js/edit_area/plugins/test/langs/bg.js +10 -0
  87. data/html/js/edit_area/plugins/test/langs/zh.js +4 -0
  88. data/html/js/edit_area/reg_syntax/java.js +56 -0
  89. data/html/js/edit_area/reg_syntax/ruby.js +9 -9
  90. data/html/js/edit_area/reg_syntax.js +15 -13
  91. data/html/js/edit_area/regexp.js +36 -32
  92. data/html/js/edit_area/resize_area.js +43 -47
  93. data/html/js/edit_area/search_replace.js +29 -29
  94. data/html/js/edit_area/template.html +6 -4
  95. data/html/js/json-broker-client.js +23 -17
  96. data/html/js/result-page.js +107 -57
  97. data/html/js/rt-setting-page.js +38 -15
  98. data/html/js/sidebar.js +41 -27
  99. data/html/js/templates.js +167 -32
  100. data/html/js/utils.js +143 -7
  101. data/html/js/yui/treeview.js +3671 -0
  102. data/html/swf/chart.swf +0 -0
  103. data/html/test/agent_editor_spec.js +815 -0
  104. data/html/test/index.html +40 -0
  105. data/html/test/jsspec/JSSpec.css +224 -0
  106. data/html/test/jsspec/JSSpec.js +1549 -0
  107. data/html/test/jsspec/diff_match_patch.js +1 -0
  108. data/html/test/utils_spec.js +111 -0
  109. data/lib/jiji/agent/agent.rb +69 -12
  110. data/lib/jiji/agent/agent_manager.rb +18 -12
  111. data/lib/jiji/agent/agent_registry.rb +35 -121
  112. data/lib/jiji/collector.rb +16 -6
  113. data/lib/jiji/command.rb +46 -5
  114. data/lib/jiji/dao/file_system_dao.rb +158 -0
  115. data/lib/jiji/dao/timed_data_dao.rb +2 -0
  116. data/lib/jiji/dao/trade_result_dao.rb +1 -1
  117. data/lib/jiji/error.rb +24 -8
  118. data/lib/jiji/migration/migrator1_2_0.rb +67 -0
  119. data/lib/jiji/models.rb +82 -24
  120. data/lib/jiji/operator.rb +55 -51
  121. data/lib/jiji/output.rb +85 -29
  122. data/lib/jiji/output_manager.rb +84 -0
  123. data/lib/jiji/plugin/embedded/single_click_client.rb +2 -2
  124. data/lib/jiji/plugin/securities_plugin.rb +0 -1
  125. data/lib/jiji/process.rb +229 -208
  126. data/lib/jiji/process_manager.rb +190 -96
  127. data/lib/jiji/registry.rb +87 -19
  128. data/lib/jiji/server.rb +1 -0
  129. data/lib/jiji/service/agent_service.rb +147 -48
  130. data/lib/jiji/service/output_service.rb +37 -17
  131. data/lib/jiji/service/process_service.rb +3 -5
  132. data/lib/jiji/service/trade_result_service.rb +4 -5
  133. data/lib/jiji/util/file_lock.rb +4 -4
  134. data/lib/jiji/util/include_proxy.rb +17 -0
  135. data/lib/jiji/util/json_broker.rb +6 -4
  136. data/lib/jiji/util/util.rb +1 -1
  137. data/swf/chart/fx/chart/Chart.as +7 -0
  138. data/swf/chart/fx/chart/ui/graph/GraphManager.as +15 -2
  139. data/test/ProcessTest/agents/foo.rb +10 -0
  140. data/test/ProcessTest/conf/configuration.yaml +3 -0
  141. data/test/agent/agent_tests.rb +10 -0
  142. data/test/agent/test_AgentManager.rb +28 -12
  143. data/test/agent/test_AgentRegistry.rb +194 -99
  144. data/test/agent/test_PeriodicallyAgent.rb +1 -2
  145. data/test/agent/test_Permitter.rb +1 -2
  146. data/test/all_tests.rb +7 -19
  147. data/test/dao/dao_tests.rb +9 -0
  148. data/test/dao/test_FileSystemDao.rb +431 -0
  149. data/test/dao/test_RateDao.rb +5 -7
  150. data/test/dao/test_TradeResultDao.rb +1 -2
  151. data/test/migration/migration_tests.rb +10 -0
  152. data/test/migration/migrator1_2_0test_data/basic/out/M2NlOTA2ODEtZDdlNi00NWU1LWIwNDQtMjBmODY2ZGNkNzBj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  153. data/test/migration/migrator1_2_0test_data/basic/out/MDVhYzcxMjYtMGFlMS00Mzk0LWEyNmUtYjVjZjgwNDA0ZmE2/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  154. data/test/migration/migrator1_2_0test_data/basic/out/MzA1YTk0NDgtNzhjNi00NDk3LTk2NTktYzE1ZjBhNzdiYjNj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  155. data/test/migration/migrator1_2_0test_data/basic/out/YjRkOTI1MzEtZTM4MS00YjQwLTg1ZTQtMWFmZDRlNDUwMzBm/56e75YuV5bmz5Z2H57ea/meta.yaml +9 -0
  156. data/test/migration/migrator1_2_0test_data/basic/props.yaml +85 -0
  157. data/test/migration/migrator1_2_0test_data/illegal_props/out/M2NlOTA2ODEtZDdlNi00NWU1LWIwNDQtMjBmODY2ZGNkNzBj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  158. data/test/migration/migrator1_2_0test_data/illegal_props/out/MDVhYzcxMjYtMGFlMS00Mzk0LWEyNmUtYjVjZjgwNDA0ZmE2/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  159. data/test/migration/migrator1_2_0test_data/illegal_props/out/MzA1YTk0NDgtNzhjNi00NDk3LTk2NTktYzE1ZjBhNzdiYjNj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  160. data/test/migration/migrator1_2_0test_data/illegal_props/out/YjRkOTI1MzEtZTM4MS00YjQwLTg1ZTQtMWFmZDRlNDUwMzBm/56e75YuV5bmz5Z2H57ea/meta.yaml +9 -0
  161. data/test/migration/migrator1_2_0test_data/illegal_props/props.yaml +1 -0
  162. data/test/migration/migrator1_2_0test_data/no_outs/props.yaml +85 -0
  163. data/test/migration/migrator1_2_0test_data/no_props/out/M2NlOTA2ODEtZDdlNi00NWU1LWIwNDQtMjBmODY2ZGNkNzBj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  164. data/test/migration/migrator1_2_0test_data/no_props/out/MDVhYzcxMjYtMGFlMS00Mzk0LWEyNmUtYjVjZjgwNDA0ZmE2/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  165. data/test/migration/migrator1_2_0test_data/no_props/out/MzA1YTk0NDgtNzhjNi00NDk3LTk2NTktYzE1ZjBhNzdiYjNj/56e75YuV5bmz5Z2H57ea/meta.yaml +8 -0
  166. data/test/migration/migrator1_2_0test_data/no_props/out/YjRkOTI1MzEtZTM4MS00YjQwLTg1ZTQtMWFmZDRlNDUwMzBm/56e75YuV5bmz5Z2H57ea/meta.yaml +9 -0
  167. data/test/migration/test_Migrator.rb +1 -1
  168. data/test/migration/test_Migrator1_0_3.rb +1 -1
  169. data/test/migration/test_Migrator1_1_0.rb +1 -1
  170. data/test/migration/test_Migrator1_2_0.rb +94 -0
  171. data/test/plugin/embedded/test_SingleClickClient.rb +1 -2
  172. data/test/plugin/plugin_tests.rb +8 -0
  173. data/test/plugin/test_Loader.rb +1 -1
  174. data/test/shared/rate.csv +144 -0
  175. data/test/shared/shared_tests.rb +9 -0
  176. data/test/shared/test_Cross.rb +144 -0
  177. data/test/shared/test_PositionManager.rb +285 -0
  178. data/test/shared/test_Signal.rb +65 -0
  179. data/test/test_Output.rb +28 -21
  180. data/test/test_OutputManager.rb +162 -0
  181. data/test/test_Output_registry.rb +6 -17
  182. data/test/test_Process.rb +434 -222
  183. data/test/test_ProcessManager.rb +458 -101
  184. data/test/test_utils.rb +71 -8
  185. data/test/util/test_BlockToSession.rb +1 -2
  186. data/test/util/test_CSV.rb +1 -2
  187. data/test/util/test_SynchronizeInterceptor.rb +1 -2
  188. data/test/util/util_tests.rb +9 -0
  189. metadata +127 -47
  190. data/html/js/agent-editor-page.js +0 -440
  191. data/html/js/codepress/codepress.css +0 -21
  192. data/html/js/codepress/codepress.html +0 -35
  193. data/html/js/codepress/codepress.js +0 -138
  194. data/html/js/codepress/engines/gecko.js +0 -293
  195. data/html/js/codepress/engines/khtml.js +0 -0
  196. data/html/js/codepress/engines/msie.js +0 -304
  197. data/html/js/codepress/engines/older.js +0 -0
  198. data/html/js/codepress/engines/opera.js +0 -260
  199. data/html/js/codepress/images/line-numbers.gif +0 -0
  200. data/html/js/codepress/index.html +0 -443
  201. data/html/js/codepress/languages/asp.css +0 -71
  202. data/html/js/codepress/languages/asp.js +0 -117
  203. data/html/js/codepress/languages/autoit.css +0 -13
  204. data/html/js/codepress/languages/autoit.js +0 -32
  205. data/html/js/codepress/languages/csharp.css +0 -9
  206. data/html/js/codepress/languages/csharp.js +0 -25
  207. data/html/js/codepress/languages/css.css +0 -10
  208. data/html/js/codepress/languages/css.js +0 -23
  209. data/html/js/codepress/languages/generic.css +0 -9
  210. data/html/js/codepress/languages/generic.js +0 -25
  211. data/html/js/codepress/languages/html.css +0 -13
  212. data/html/js/codepress/languages/html.js +0 -59
  213. data/html/js/codepress/languages/java.css +0 -7
  214. data/html/js/codepress/languages/java.js +0 -24
  215. data/html/js/codepress/languages/javascript.css +0 -8
  216. data/html/js/codepress/languages/javascript.js +0 -30
  217. data/html/js/codepress/languages/perl.css +0 -11
  218. data/html/js/codepress/languages/perl.js +0 -27
  219. data/html/js/codepress/languages/php.css +0 -12
  220. data/html/js/codepress/languages/php.js +0 -61
  221. data/html/js/codepress/languages/ruby.css +0 -10
  222. data/html/js/codepress/languages/ruby.js +0 -26
  223. data/html/js/codepress/languages/sql.css +0 -10
  224. data/html/js/codepress/languages/sql.js +0 -30
  225. data/html/js/codepress/languages/text.css +0 -5
  226. data/html/js/codepress/languages/text.js +0 -9
  227. data/html/js/codepress/languages/vbscript.css +0 -71
  228. data/html/js/codepress/languages/vbscript.js +0 -117
  229. data/html/js/codepress/languages/xsl.css +0 -15
  230. data/html/js/codepress/languages/xsl.js +0 -103
  231. data/html/js/codepress/license.txt +0 -458
  232. data/html/js/edit_area/images/Thumbs.db +0 -0
  233. data/test/plugin/test_gem/a/lib/jiji_plugin_test.rb +0 -6
@@ -0,0 +1,1549 @@
1
+ /**
2
+ * JSSpec
3
+ *
4
+ * Copyright 2007 Alan Kang
5
+ * - mailto:jania902@gmail.com
6
+ * - http://jania.pe.kr
7
+ *
8
+ * http://jania.pe.kr/aw/moin.cgi/JSSpec
9
+ *
10
+ * Dependencies:
11
+ * - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
12
+ *
13
+ * This library is free software; you can redistribute it and/or
14
+ * modify it under the terms of the GNU Lesser General Public
15
+ * License as published by the Free Software Foundation; either
16
+ * version 2.1 of the License, or (at your option) any later version.
17
+ *
18
+ * This library is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21
+ * Lesser General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU Lesser General Public
24
+ * License along with this library; if not, write to the Free Software
25
+ * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26
+ */
27
+
28
+ /**
29
+ * Namespace
30
+ */
31
+
32
+ var JSSpec = {
33
+ specs: [],
34
+
35
+ EMPTY_FUNCTION: function() {},
36
+
37
+ Browser: {
38
+ // By Rendering Engines
39
+ Trident: navigator.appName === "Microsoft Internet Explorer",
40
+ Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
41
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
42
+ KHTML: navigator.userAgent.indexOf('KHTML') !== -1,
43
+ Presto: navigator.appName === "Opera",
44
+
45
+ // By Platforms
46
+ Mac: navigator.userAgent.indexOf("Macintosh") !== -1,
47
+ Ubuntu: navigator.userAgent.indexOf('Ubuntu') !== -1,
48
+ Win: navigator.userAgent.indexOf('Windows') !== -1,
49
+
50
+ // By Browsers
51
+ IE: navigator.appName === "Microsoft Internet Explorer",
52
+ IE6: navigator.userAgent.indexOf('MSIE 6') !== -1,
53
+ IE7: navigator.userAgent.indexOf('MSIE 7') !== -1,
54
+ IE8: navigator.userAgent.indexOf('MSIE 8') !== -1,
55
+
56
+ FF: navigator.userAgent.indexOf('Firefox') !== -1,
57
+ FF2: navigator.userAgent.indexOf('Firefox/2') !== -1,
58
+ FF3: navigator.userAgent.indexOf('Firefox/3') !== -1,
59
+ Safari: navigator.userAgent.indexOf('Safari') !== -1
60
+ }
61
+ };
62
+
63
+
64
+
65
+ /**
66
+ * Executor
67
+ */
68
+ JSSpec.Executor = function(target, onSuccess, onException) {
69
+ this.target = target;
70
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
71
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
72
+
73
+ if(JSSpec.Browser.Trident) {
74
+ // Exception handler for Trident. It helps to collect exact line number where exception occured.
75
+ window.onerror = function(message, fileName, lineNumber) {
76
+ var self = window._curExecutor;
77
+ var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
78
+
79
+ if(JSSpec._secondPass) {
80
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
81
+ delete JSSpec._secondPass;
82
+ delete JSSpec._assertionFailure;
83
+
84
+ ex.type = "failure";
85
+ self.onException(self, ex);
86
+ } else if(JSSpec._assertionFailure) {
87
+ JSSpec._secondPass = true;
88
+ self.run();
89
+ } else {
90
+ self.onException(self, ex);
91
+ }
92
+
93
+ return true;
94
+ };
95
+ }
96
+ };
97
+ JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
98
+ var merged = {
99
+ message:assertionFailure.message,
100
+ fileName:normalException.fileName,
101
+ lineNumber:normalException.lineNumber
102
+ };
103
+
104
+ return merged;
105
+ };
106
+
107
+ JSSpec.Executor.prototype.run = function() {
108
+ var self = this;
109
+ var target = this.target;
110
+ var onSuccess = this.onSuccess;
111
+ var onException = this.onException;
112
+
113
+ window.setTimeout(
114
+ function() {
115
+ var result;
116
+ if(JSSpec.Browser.Trident) {
117
+ window._curExecutor = self;
118
+
119
+ result = self.target();
120
+ self.onSuccess(self, result);
121
+ } else {
122
+ try {
123
+ result = self.target();
124
+ self.onSuccess(self, result);
125
+ } catch(ex) {
126
+ if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
127
+
128
+ if(JSSpec._secondPass) {
129
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
130
+ delete JSSpec._secondPass;
131
+ delete JSSpec._assertionFailure;
132
+
133
+ ex.type = "failure";
134
+ self.onException(self, ex);
135
+ } else if(JSSpec._assertionFailure) {
136
+ JSSpec._secondPass = true;
137
+ self.run();
138
+ } else {
139
+ self.onException(self, ex);
140
+ }
141
+ }
142
+ }
143
+ },
144
+ 0
145
+ );
146
+ };
147
+
148
+
149
+
150
+ /**
151
+ * CompositeExecutor composites one or more executors and execute them sequencially.
152
+ */
153
+ JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
154
+ this.queue = [];
155
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
156
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
157
+ this.continueOnException = !!continueOnException;
158
+ };
159
+
160
+ JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
161
+ this.addExecutor(new JSSpec.Executor(func));
162
+ };
163
+
164
+ JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
165
+ var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
166
+ if(last) {
167
+ last.next = executor;
168
+ }
169
+
170
+ executor.parent = this;
171
+ executor.onSuccessBackup = executor.onSuccess;
172
+ executor.onSuccess = function(result) {
173
+ this.onSuccessBackup(result);
174
+ if(this.next) {
175
+ this.next.run();
176
+ } else {
177
+ this.parent.onSuccess();
178
+ }
179
+ };
180
+ executor.onExceptionBackup = executor.onException;
181
+ executor.onException = function(executor, ex) {
182
+ this.onExceptionBackup(executor, ex);
183
+
184
+ if(this.parent.continueOnException) {
185
+ if(this.next) {
186
+ this.next.run();
187
+ } else {
188
+ this.parent.onSuccess();
189
+ }
190
+ } else {
191
+ this.parent.onException(executor, ex);
192
+ }
193
+ };
194
+
195
+ this.queue.push(executor);
196
+ };
197
+
198
+ JSSpec.CompositeExecutor.prototype.run = function() {
199
+ if(this.queue.length > 0) {
200
+ this.queue[0].run();
201
+ }
202
+ };
203
+
204
+ /**
205
+ * Spec is a set of Examples in a specific context
206
+ */
207
+ JSSpec.Spec = function(context, entries) {
208
+ this.id = JSSpec.Spec.id++;
209
+ this.context = context;
210
+ this.url = location.href;
211
+
212
+ this.filterEntriesByEmbeddedExpressions(entries);
213
+ this.extractOutSpecialEntries(entries);
214
+ this.examples = this.makeExamplesFromEntries(entries);
215
+ this.examplesMap = this.makeMapFromExamples(this.examples);
216
+ };
217
+
218
+ JSSpec.Spec.id = 0;
219
+ JSSpec.Spec.prototype.getExamples = function() {
220
+ return this.examples;
221
+ };
222
+
223
+ JSSpec.Spec.prototype.hasException = function() {
224
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
225
+ };
226
+
227
+ JSSpec.Spec.prototype.getTotalFailures = function() {
228
+ var examples = this.examples;
229
+ var failures = 0;
230
+ for(var i = 0; i < examples.length; i++) {
231
+ if(examples[i].isFailure()) failures++;
232
+ }
233
+ return failures;
234
+ };
235
+
236
+ JSSpec.Spec.prototype.getTotalErrors = function() {
237
+ var examples = this.examples;
238
+ var errors = 0;
239
+ for(var i = 0; i < examples.length; i++) {
240
+ if(examples[i].isError()) errors++;
241
+ }
242
+ return errors;
243
+ };
244
+
245
+ JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
246
+ var isTrue;
247
+ for(name in entries) if(entries.hasOwnProperty(name)) {
248
+ var m = name.match(/\[\[(.+)\]\]/);
249
+ if(m && m[1]) {
250
+ eval("isTrue = (" + m[1] + ")");
251
+ if(!isTrue) delete entries[name];
252
+ }
253
+ }
254
+ };
255
+
256
+ JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
257
+ this.beforeEach = JSSpec.EMPTY_FUNCTION;
258
+ this.beforeAll = JSSpec.EMPTY_FUNCTION;
259
+ this.afterEach = JSSpec.EMPTY_FUNCTION;
260
+ this.afterAll = JSSpec.EMPTY_FUNCTION;
261
+
262
+ for(name in entries) if(entries.hasOwnProperty(name)) {
263
+ if(name == 'before' || name == 'before each' || name == 'before_each') {
264
+ this.beforeEach = entries[name];
265
+ } else if(name == 'before all' || name == 'before_all') {
266
+ this.beforeAll = entries[name];
267
+ } else if(name == 'after' || name == 'after each' || name == 'after_each') {
268
+ this.afterEach = entries[name];
269
+ } else if(name == 'after all' || name == 'after_all') {
270
+ this.afterAll = entries[name];
271
+ }
272
+ }
273
+
274
+ delete entries['before'];
275
+ delete entries['before each'];
276
+ delete entries['before_each'];
277
+ delete entries['before all'];
278
+ delete entries['before_all'];
279
+ delete entries['after'];
280
+ delete entries['after each'];
281
+ delete entries['after_each'];
282
+ delete entries['after all'];
283
+ delete entries['after_all'];
284
+ };
285
+
286
+ JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
287
+ var examples = [];
288
+ for(name in entries) if(entries.hasOwnProperty(name)) {
289
+ examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
290
+ }
291
+ return examples;
292
+ };
293
+
294
+ JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
295
+ var map = {};
296
+ for(var i = 0; i < examples.length; i++) {
297
+ var example = examples[i];
298
+ map[example.id] = examples[i];
299
+ }
300
+ return map;
301
+ };
302
+
303
+ JSSpec.Spec.prototype.getExampleById = function(id) {
304
+ return this.examplesMap[id];
305
+ };
306
+
307
+ JSSpec.Spec.prototype.getExecutor = function() {
308
+ var self = this;
309
+ var onException = function(executor, ex) {
310
+ self.exception = ex;
311
+ };
312
+
313
+ var composite = new JSSpec.CompositeExecutor();
314
+ composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
315
+ composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
316
+ self.exception = ex;
317
+ JSSpec.log.onSpecEnd(self);
318
+ }));
319
+
320
+ var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
321
+ for(var i = 0; i < this.examples.length; i++) {
322
+ exampleAndAfter.addExecutor(this.examples[i].getExecutor());
323
+ }
324
+ exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
325
+ exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
326
+ composite.addExecutor(exampleAndAfter);
327
+
328
+ return composite;
329
+ };
330
+
331
+ /**
332
+ * Example
333
+ */
334
+ JSSpec.Example = function(name, target, before, after) {
335
+ this.id = JSSpec.Example.id++;
336
+ this.name = name;
337
+ this.target = target;
338
+ this.before = before;
339
+ this.after = after;
340
+ };
341
+
342
+ JSSpec.Example.id = 0;
343
+ JSSpec.Example.prototype.isFailure = function() {
344
+ return this.exception && this.exception.type == "failure";
345
+ };
346
+
347
+ JSSpec.Example.prototype.isError = function() {
348
+ return this.exception && !this.exception.type;
349
+ };
350
+
351
+ JSSpec.Example.prototype.getExecutor = function() {
352
+ var self = this;
353
+ var onException = function(executor, ex) {
354
+ self.exception = ex;
355
+ };
356
+
357
+ var composite = new JSSpec.CompositeExecutor();
358
+ composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
359
+ composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
360
+ self.exception = ex;
361
+ JSSpec.log.onExampleEnd(self);
362
+ }));
363
+
364
+ var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
365
+
366
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
367
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
368
+ targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
369
+
370
+ composite.addExecutor(targetAndAfter);
371
+
372
+ return composite;
373
+ };
374
+
375
+ /**
376
+ * Runner
377
+ */
378
+ JSSpec.Runner = function(specs, logger) {
379
+ JSSpec.log = logger;
380
+
381
+ this.totalExamples = 0;
382
+ this.specs = [];
383
+ this.specsMap = {};
384
+ this.addAllSpecs(specs);
385
+ };
386
+
387
+ JSSpec.Runner.prototype.addAllSpecs = function(specs) {
388
+ for(var i = 0; i < specs.length; i++) {
389
+ this.addSpec(specs[i]);
390
+ }
391
+ };
392
+
393
+ JSSpec.Runner.prototype.addSpec = function(spec) {
394
+ this.specs.push(spec);
395
+ this.specsMap[spec.id] = spec;
396
+ this.totalExamples += spec.getExamples().length;
397
+ };
398
+
399
+ JSSpec.Runner.prototype.getSpecById = function(id) {
400
+ return this.specsMap[id];
401
+ };
402
+
403
+ JSSpec.Runner.prototype.getSpecByContext = function(context) {
404
+ for(var i = 0; i < this.specs.length; i++) {
405
+ if(this.specs[i].context == context) return this.specs[i];
406
+ }
407
+ return null;
408
+ };
409
+
410
+ JSSpec.Runner.prototype.getSpecs = function() {
411
+ return this.specs;
412
+ };
413
+
414
+ JSSpec.Runner.prototype.hasException = function() {
415
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
416
+ };
417
+
418
+ JSSpec.Runner.prototype.getTotalFailures = function() {
419
+ var specs = this.specs;
420
+ var failures = 0;
421
+ for(var i = 0; i < specs.length; i++) {
422
+ failures += specs[i].getTotalFailures();
423
+ }
424
+ return failures;
425
+ };
426
+
427
+ JSSpec.Runner.prototype.getTotalErrors = function() {
428
+ var specs = this.specs;
429
+ var errors = 0;
430
+ for(var i = 0; i < specs.length; i++) {
431
+ errors += specs[i].getTotalErrors();
432
+ }
433
+ return errors;
434
+ };
435
+
436
+
437
+ JSSpec.Runner.prototype.run = function() {
438
+ JSSpec.log.onRunnerStart();
439
+ var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
440
+ for(var i = 0; i < this.specs.length; i++) {
441
+ executor.addExecutor(this.specs[i].getExecutor());
442
+ }
443
+ executor.run();
444
+ };
445
+
446
+
447
+ JSSpec.Runner.prototype.rerun = function(context) {
448
+ JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
449
+ JSSpec.runner.run();
450
+ };
451
+
452
+ /**
453
+ * Logger
454
+ */
455
+ JSSpec.Logger = function() {
456
+ this.finishedExamples = 0;
457
+ this.startedAt = null;
458
+ };
459
+
460
+ JSSpec.Logger.prototype.onRunnerStart = function() {
461
+ this._title = document.title;
462
+
463
+ this.startedAt = new Date();
464
+ var container = document.getElementById('jsspec_container');
465
+ if(container) {
466
+ container.innerHTML = "";
467
+ } else {
468
+ container = document.createElement("DIV");
469
+ container.id = "jsspec_container";
470
+ document.body.appendChild(container);
471
+ }
472
+
473
+ var title = document.createElement("DIV");
474
+ title.id = "title";
475
+ title.innerHTML = [
476
+ '<h1>JSSpec</h1>',
477
+ '<ul>',
478
+ JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
479
+ ' <li><span id="total_examples">' + JSSpec.runner.totalExamples + '</span> examples</li>',
480
+ ' <li><span id="total_failures">0</span> failures</li>',
481
+ ' <li><span id="total_errors">0</span> errors</li>',
482
+ ' <li><span id="progress">0</span>% done</li>',
483
+ ' <li><span id="total_elapsed">0</span> secs</li>',
484
+ '</ul>',
485
+ '<p><a href="http://jania.pe.kr/aw/moin.cgi/JSSpec">JSSpec homepage</a></p>',
486
+ ].join("");
487
+ container.appendChild(title);
488
+
489
+ var list = document.createElement("DIV");
490
+ list.id = "list";
491
+ list.innerHTML = [
492
+ '<h2>List</h2>',
493
+ '<ul class="specs">',
494
+ function() {
495
+ var specs = JSSpec.runner.getSpecs();
496
+ var sb = [];
497
+ for(var i = 0; i < specs.length; i++) {
498
+ var spec = specs[i];
499
+ sb.push('<li id="spec_' + specs[i].id + '_list"><h3><a href="#spec_' + specs[i].id + '">' + JSSpec.util.escapeTags(specs[i].context) + '</a> [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3> </li>');
500
+ }
501
+ return sb.join("");
502
+ }(),
503
+ '</ul>'
504
+ ].join("");
505
+ container.appendChild(list);
506
+
507
+ var log = document.createElement("DIV");
508
+ log.id = "log";
509
+ log.innerHTML = [
510
+ '<h2>Log</h2>',
511
+ '<ul class="specs">',
512
+ function() {
513
+ var specs = JSSpec.runner.getSpecs();
514
+ var sb = [];
515
+ for(var i = 0; i < specs.length; i++) {
516
+ var spec = specs[i];
517
+ sb.push(' <li id="spec_' + specs[i].id + '">');
518
+ sb.push(' <h3>' + JSSpec.util.escapeTags(specs[i].context) + ' [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3>');
519
+ sb.push(' <ul id="spec_' + specs[i].id + '_examples" class="examples">');
520
+ for(var j = 0; j < spec.examples.length; j++) {
521
+ var example = spec.examples[j];
522
+ sb.push(' <li id="example_' + example.id + '">');
523
+ sb.push(' <h4>' + JSSpec.util.escapeTags(example.name) + '</h4>');
524
+ sb.push(' <pre class="examples-code"><code>'+JSSpec.util.escapeTags(example.target.toString())+'</code></pre>');
525
+ sb.push(' </li>');
526
+ }
527
+ sb.push(' </ul>');
528
+ sb.push(' </li>');
529
+ }
530
+ return sb.join("");
531
+ }(),
532
+ '</ul>'
533
+ ].join("");
534
+
535
+ container.appendChild(log);
536
+
537
+ // add event handler for toggling
538
+ var specs = JSSpec.runner.getSpecs();
539
+ var sb = [];
540
+ for(var i = 0; i < specs.length; i++) {
541
+ var spec = document.getElementById("spec_" + specs[i].id);
542
+ var title = spec.getElementsByTagName("H3")[0];
543
+ title.onclick = function(e) {
544
+ var target = document.getElementById(this.parentNode.id + "_examples");
545
+ target.style.display = target.style.display == "none" ? "block" : "none";
546
+ return true;
547
+ }
548
+ }
549
+ };
550
+
551
+ JSSpec.Logger.prototype.onRunnerEnd = function() {
552
+ if(JSSpec.runner.hasException()) {
553
+ var times = 4;
554
+ var title1 = "*" + this._title;
555
+ var title2 = "*F" + JSSpec.runner.getTotalFailures() + " E" + JSSpec.runner.getTotalErrors() + "* " + this._title;
556
+ } else {
557
+ var times = 2;
558
+ var title1 = this._title;
559
+ var title2 = "Success";
560
+ }
561
+ this.blinkTitle(times,title1,title2);
562
+ };
563
+
564
+ JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
565
+ var times = times * 2;
566
+ var mode = true;
567
+
568
+ var f = function() {
569
+ if(times > 0) {
570
+ document.title = mode ? title1 : title2;
571
+ mode = !mode;
572
+ times--;
573
+ window.setTimeout(f, 500);
574
+ } else {
575
+ document.title = title1;
576
+ }
577
+ };
578
+
579
+ f();
580
+ };
581
+
582
+ JSSpec.Logger.prototype.onSpecStart = function(spec) {
583
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
584
+ var spec_log = document.getElementById("spec_" + spec.id);
585
+
586
+ spec_list.className = "ongoing";
587
+ spec_log.className = "ongoing";
588
+ };
589
+
590
+ JSSpec.Logger.prototype.onSpecEnd = function(spec) {
591
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
592
+ var spec_log = document.getElementById("spec_" + spec.id);
593
+ var examples = document.getElementById("spec_" + spec.id + "_examples");
594
+ var className = spec.hasException() ? "exception" : "success";
595
+
596
+ spec_list.className = className;
597
+ spec_log.className = className;
598
+
599
+ if(JSSpec.options.autocollapse && !spec.hasException()) examples.style.display = "none";
600
+
601
+ if(spec.exception) {
602
+ spec_log.appendChild(document.createTextNode(" - " + spec.exception.message));
603
+ }
604
+ };
605
+
606
+ JSSpec.Logger.prototype.onExampleStart = function(example) {
607
+ var li = document.getElementById("example_" + example.id);
608
+ li.className = "ongoing";
609
+ };
610
+
611
+ JSSpec.Logger.prototype.onExampleEnd = function(example) {
612
+ var li = document.getElementById("example_" + example.id);
613
+ li.className = example.exception ? "exception" : "success";
614
+
615
+ if(example.exception) {
616
+ var div = document.createElement("DIV");
617
+ div.innerHTML = example.exception.message + "<p><br />" + " at " + example.exception.fileName + ", line " + example.exception.lineNumber + "</p>";
618
+ li.appendChild(div);
619
+ }
620
+
621
+ var title = document.getElementById("title");
622
+ var runner = JSSpec.runner;
623
+
624
+ title.className = runner.hasException() ? "exception" : "success";
625
+
626
+ this.finishedExamples++;
627
+ document.getElementById("total_failures").innerHTML = runner.getTotalFailures();
628
+ document.getElementById("total_errors").innerHTML = runner.getTotalErrors();
629
+ var progress = parseInt(this.finishedExamples / runner.totalExamples * 100);
630
+ document.getElementById("progress").innerHTML = progress;
631
+ document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
632
+
633
+ document.title = progress + "%: " + this._title;
634
+ };
635
+
636
+ /**
637
+ * IncludeMatcher
638
+ */
639
+ JSSpec.IncludeMatcher = function(actual, expected, condition) {
640
+ this.actual = actual;
641
+ this.expected = expected;
642
+ this.condition = condition;
643
+ this.match = false;
644
+ this.explaination = this.makeExplain();
645
+ };
646
+
647
+ JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) {
648
+ return new JSSpec.IncludeMatcher(actual, expected, condition);
649
+ };
650
+
651
+ JSSpec.IncludeMatcher.prototype.matches = function() {
652
+ return this.match;
653
+ };
654
+
655
+ JSSpec.IncludeMatcher.prototype.explain = function() {
656
+ return this.explaination;
657
+ };
658
+
659
+ JSSpec.IncludeMatcher.prototype.makeExplain = function() {
660
+ if(typeof this.actual.length == 'undefined') {
661
+ return this.makeExplainForNotArray();
662
+ } else {
663
+ return this.makeExplainForArray();
664
+ }
665
+ };
666
+
667
+ JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
668
+ if(this.condition) {
669
+ this.match = !!this.actual[this.expected];
670
+ } else {
671
+ this.match = !this.actual[this.expected];
672
+ }
673
+
674
+ var sb = [];
675
+ sb.push('<p>actual value:</p>');
676
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.expected) + '</p>');
677
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
678
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
679
+ return sb.join("");
680
+ };
681
+
682
+ JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
683
+ var matches;
684
+ if(this.condition) {
685
+ for(var i = 0; i < this.actual.length; i++) {
686
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
687
+ if(matches) {
688
+ this.match = true;
689
+ break;
690
+ }
691
+ }
692
+ } else {
693
+ this.match = true;
694
+ for(var i = 0; i < this.actual.length; i++) {
695
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
696
+ if(matches) {
697
+ this.match = false;
698
+ break;
699
+ }
700
+ }
701
+ }
702
+
703
+ if(this.match) return "";
704
+
705
+ var sb = [];
706
+ sb.push('<p>actual value:</p>');
707
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '</p>');
708
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
709
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
710
+ return sb.join("");
711
+ };
712
+
713
+ /**
714
+ * PropertyLengthMatcher
715
+ */
716
+ JSSpec.PropertyLengthMatcher = function(num, property, o, condition) {
717
+ this.num = num;
718
+ this.o = o;
719
+ this.property = property;
720
+ if((property == 'characters' || property == 'items') && typeof o.length != 'undefined') {
721
+ this.property = 'length';
722
+ }
723
+
724
+ this.condition = condition;
725
+ this.conditionMet = function(x) {
726
+ if(condition == 'exactly') return x.length == num;
727
+ if(condition == 'at least') return x.length >= num;
728
+ if(condition == 'at most') return x.length <= num;
729
+
730
+ throw "Unknown condition '" + condition + "'";
731
+ };
732
+ this.match = false;
733
+ this.explaination = this.makeExplain();
734
+ };
735
+
736
+ JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() {
737
+ if(this.o._type == 'String' && this.property == 'length') {
738
+ this.match = this.conditionMet(this.o);
739
+ return this.match ? '' : this.makeExplainForString();
740
+ } else if(typeof this.o.length != 'undefined' && this.property == "length") {
741
+ this.match = this.conditionMet(this.o);
742
+ return this.match ? '' : this.makeExplainForArray();
743
+ } else if(typeof this.o[this.property] != 'undefined' && this.o[this.property] != null) {
744
+ this.match = this.conditionMet(this.o[this.property]);
745
+ return this.match ? '' : this.makeExplainForObject();
746
+ } else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) {
747
+ this.match = false;
748
+ return this.makeExplainForNoProperty();
749
+ }
750
+
751
+ this.match = true;
752
+ };
753
+
754
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() {
755
+ var sb = [];
756
+
757
+ var exp = this.num == 0 ?
758
+ 'be an <strong>empty string</strong>' :
759
+ 'have <strong>' + this.condition + ' ' + this.num + ' characters</strong>';
760
+
761
+ sb.push('<p>actual value has <strong>' + this.o.length + ' characters</strong>:</p>');
762
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
763
+ sb.push('<p>but it should ' + exp + '.</p>');
764
+
765
+ return sb.join("");
766
+ };
767
+
768
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() {
769
+ var sb = [];
770
+
771
+ var exp = this.num == 0 ?
772
+ 'be an <strong>empty array</strong>' :
773
+ 'have <strong>' + this.condition + ' ' + this.num + ' items</strong>';
774
+
775
+ sb.push('<p>actual value has <strong>' + this.o.length + ' items</strong>:</p>');
776
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
777
+ sb.push('<p>but it should ' + exp + '.</p>');
778
+
779
+ return sb.join("");
780
+ };
781
+
782
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() {
783
+ var sb = [];
784
+
785
+ var exp = this.num == 0 ?
786
+ 'be <strong>empty</strong>' :
787
+ 'have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '.</strong>';
788
+
789
+ sb.push('<p>actual value has <strong>' + this.o[this.property].length + ' ' + this.property + '</strong>:</p>');
790
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o, false, this.property) + '</p>');
791
+ sb.push('<p>but it should ' + exp + '.</p>');
792
+
793
+ return sb.join("");
794
+ };
795
+
796
+ JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() {
797
+ var sb = [];
798
+
799
+ sb.push('<p>actual value:</p>');
800
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
801
+ sb.push('<p>should have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '</strong> but there\'s no such property.</p>');
802
+
803
+ return sb.join("");
804
+ };
805
+
806
+ JSSpec.PropertyLengthMatcher.prototype.matches = function() {
807
+ return this.match;
808
+ };
809
+
810
+ JSSpec.PropertyLengthMatcher.prototype.explain = function() {
811
+ return this.explaination;
812
+ };
813
+
814
+ JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) {
815
+ return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
816
+ };
817
+
818
+ /**
819
+ * EqualityMatcher
820
+ */
821
+ JSSpec.EqualityMatcher = {};
822
+
823
+ JSSpec.EqualityMatcher.createInstance = function(expected, actual) {
824
+ if(expected == null || actual == null) {
825
+ return new JSSpec.NullEqualityMatcher(expected, actual);
826
+ } else if(expected._type && expected._type == actual._type) {
827
+ if(expected._type == "String") {
828
+ return new JSSpec.StringEqualityMatcher(expected, actual);
829
+ } else if(expected._type == "Date") {
830
+ return new JSSpec.DateEqualityMatcher(expected, actual);
831
+ } else if(expected._type == "Number") {
832
+ return new JSSpec.NumberEqualityMatcher(expected, actual);
833
+ } else if(expected._type == "Array") {
834
+ return new JSSpec.ArrayEqualityMatcher(expected, actual);
835
+ } else if(expected._type == "Boolean") {
836
+ return new JSSpec.BooleanEqualityMatcher(expected, actual);
837
+ }
838
+ }
839
+
840
+ return new JSSpec.ObjectEqualityMatcher(expected, actual);
841
+ };
842
+
843
+ JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) {
844
+ var sb = [];
845
+
846
+ sb.push(actualDesc || '<p>actual value:</p>');
847
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(actual) + '</p>');
848
+ sb.push(expectedDesc || '<p>should be:</p>');
849
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(expected) + '</p>');
850
+
851
+ return sb.join("");
852
+ };
853
+
854
+ JSSpec.EqualityMatcher.diffExplain = function(expected, actual) {
855
+ var sb = [];
856
+
857
+ sb.push('<p>diff:</p>');
858
+ sb.push('<p style="margin-left:2em;">');
859
+
860
+ var dmp = new diff_match_patch();
861
+ var diff = dmp.diff_main(expected, actual);
862
+ dmp.diff_cleanupEfficiency(diff);
863
+
864
+ sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
865
+
866
+ sb.push('</p>');
867
+
868
+ return sb.join("");
869
+ };
870
+
871
+ /**
872
+ * BooleanEqualityMatcher
873
+ */
874
+ JSSpec.BooleanEqualityMatcher = function(expected, actual) {
875
+ this.expected = expected;
876
+ this.actual = actual;
877
+ };
878
+
879
+ JSSpec.BooleanEqualityMatcher.prototype.explain = function() {
880
+ var sb = [];
881
+
882
+ sb.push('<p>actual value:</p>');
883
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
884
+ sb.push('<p>should be:</p>');
885
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
886
+
887
+ return sb.join("");
888
+ };
889
+
890
+ JSSpec.BooleanEqualityMatcher.prototype.matches = function() {
891
+ return this.expected == this.actual;
892
+ };
893
+
894
+ /**
895
+ * NullEqualityMatcher
896
+ */
897
+ JSSpec.NullEqualityMatcher = function(expected, actual) {
898
+ this.expected = expected;
899
+ this.actual = actual;
900
+ };
901
+
902
+ JSSpec.NullEqualityMatcher.prototype.matches = function() {
903
+ return this.expected == this.actual && typeof this.expected == typeof this.actual;
904
+ };
905
+
906
+ JSSpec.NullEqualityMatcher.prototype.explain = function() {
907
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
908
+ };
909
+
910
+ JSSpec.DateEqualityMatcher = function(expected, actual) {
911
+ this.expected = expected;
912
+ this.actual = actual;
913
+ };
914
+
915
+ JSSpec.DateEqualityMatcher.prototype.matches = function() {
916
+ return this.expected.getTime() == this.actual.getTime();
917
+ };
918
+
919
+ JSSpec.DateEqualityMatcher.prototype.explain = function() {
920
+ var sb = [];
921
+
922
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
923
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
924
+
925
+ return sb.join("");
926
+ };
927
+
928
+ /**
929
+ * ObjectEqualityMatcher
930
+ */
931
+ JSSpec.ObjectEqualityMatcher = function(expected, actual) {
932
+ this.expected = expected;
933
+ this.actual = actual;
934
+ this.match = this.expected == this.actual;
935
+ this.explaination = this.makeExplain();
936
+ };
937
+
938
+ JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match};
939
+
940
+ JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination};
941
+
942
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() {
943
+ if(this.expected == this.actual) {
944
+ this.match = true;
945
+ return "";
946
+ }
947
+
948
+ if(JSSpec.util.isDomNode(this.expected)) {
949
+ return this.makeExplainForDomNode();
950
+ }
951
+
952
+ var key, expectedHasItem, actualHasItem;
953
+
954
+ for(key in this.expected) {
955
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
956
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
957
+ if(expectedHasItem && !actualHasItem) return this.makeExplainForMissingItem(key);
958
+ }
959
+ for(key in this.actual) {
960
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
961
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
962
+ if(actualHasItem && !expectedHasItem) return this.makeExplainForUnknownItem(key);
963
+ }
964
+
965
+ for(key in this.expected) {
966
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
967
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
968
+ }
969
+
970
+ this.match = true;
971
+ };
972
+
973
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) {
974
+ var sb = [];
975
+
976
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
977
+
978
+ return sb.join("");
979
+ };
980
+
981
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) {
982
+ var sb = [];
983
+
984
+ sb.push('<p>actual value has no item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
985
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
986
+ sb.push('<p>but it should have the item whose value is <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
987
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
988
+
989
+ return sb.join("");
990
+ };
991
+
992
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) {
993
+ var sb = [];
994
+
995
+ sb.push('<p>actual value has item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
996
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
997
+ sb.push('<p>but there should be no such item</p>');
998
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
999
+
1000
+ return sb.join("");
1001
+ };
1002
+
1003
+ JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) {
1004
+ var sb = [];
1005
+
1006
+ sb.push('<p>actual value has an item named <strong>' + JSSpec.util.inspect(key) + '</strong> whose value is <strong>' + JSSpec.util.inspect(this.actual[key]) + '</strong></p>');
1007
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
1008
+ sb.push('<p>but it\'s value should be <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
1009
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
1010
+
1011
+ return sb.join("");
1012
+ };
1013
+
1014
+
1015
+
1016
+
1017
+ /**
1018
+ * ArrayEqualityMatcher
1019
+ */
1020
+ JSSpec.ArrayEqualityMatcher = function(expected, actual) {
1021
+ this.expected = expected;
1022
+ this.actual = actual;
1023
+ this.match = this.expected == this.actual;
1024
+ this.explaination = this.makeExplain();
1025
+ };
1026
+
1027
+ JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match};
1028
+
1029
+ JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination};
1030
+
1031
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() {
1032
+ if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
1033
+
1034
+ for(var i = 0; i < this.expected.length; i++) {
1035
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
1036
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
1037
+ }
1038
+
1039
+ this.match = true;
1040
+ };
1041
+
1042
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() {
1043
+ return JSSpec.EqualityMatcher.basicExplain(
1044
+ this.expected,
1045
+ this.actual,
1046
+ '<p>but it should be <strong>' + this.expected.length + '</strong></p>',
1047
+ '<p>actual value has <strong>' + this.actual.length + '</strong> items</p>'
1048
+ );
1049
+ };
1050
+
1051
+ JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) {
1052
+ var postfix = ["th", "st", "nd", "rd", "th"][Math.min((index + 1) % 10,4)];
1053
+
1054
+ var sb = [];
1055
+
1056
+ sb.push('<p>' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is <strong>' + JSSpec.util.inspect(this.actual[index]) + '</strong>:</p>');
1057
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, index) + '</p>');
1058
+ sb.push('<p>but it should be <strong>' + JSSpec.util.inspect(this.expected[index]) + '</strong>:</p>');
1059
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, index) + '</p>');
1060
+
1061
+ return sb.join("");
1062
+ };
1063
+
1064
+ /**
1065
+ * NumberEqualityMatcher
1066
+ */
1067
+ JSSpec.NumberEqualityMatcher = function(expected, actual) {
1068
+ this.expected = expected;
1069
+ this.actual = actual;
1070
+ };
1071
+
1072
+ JSSpec.NumberEqualityMatcher.prototype.matches = function() {
1073
+ if(this.expected == this.actual) return true;
1074
+ };
1075
+
1076
+ JSSpec.NumberEqualityMatcher.prototype.explain = function() {
1077
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
1078
+ };
1079
+
1080
+ /**
1081
+ * StringEqualityMatcher
1082
+ */
1083
+ JSSpec.StringEqualityMatcher = function(expected, actual) {
1084
+ this.expected = expected;
1085
+ this.actual = actual;
1086
+ };
1087
+
1088
+ JSSpec.StringEqualityMatcher.prototype.matches = function() {
1089
+ return this.expected == this.actual;
1090
+ };
1091
+
1092
+ JSSpec.StringEqualityMatcher.prototype.explain = function() {
1093
+ var sb = [];
1094
+
1095
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
1096
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));
1097
+ return sb.join("");
1098
+ };
1099
+
1100
+ /**
1101
+ * PatternMatcher
1102
+ */
1103
+ JSSpec.PatternMatcher = function(actual, pattern, condition) {
1104
+ this.actual = actual;
1105
+ this.pattern = pattern;
1106
+ this.condition = condition;
1107
+ this.match = false;
1108
+ this.explaination = this.makeExplain();
1109
+ };
1110
+
1111
+ JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) {
1112
+ return new JSSpec.PatternMatcher(actual, pattern, condition);
1113
+ };
1114
+
1115
+ JSSpec.PatternMatcher.prototype.makeExplain = function() {
1116
+ var sb;
1117
+ if(this.actual == null || this.actual._type != 'String') {
1118
+ sb = [];
1119
+ sb.push('<p>actual value:</p>');
1120
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
1121
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
1122
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
1123
+ sb.push('<p>but pattern matching cannot be performed.</p>');
1124
+ return sb.join("");
1125
+ } else {
1126
+ this.match = this.condition == !!this.actual.match(this.pattern);
1127
+ if(this.match) return "";
1128
+
1129
+ sb = [];
1130
+ sb.push('<p>actual value:</p>');
1131
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
1132
+ sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
1133
+ sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
1134
+ return sb.join("");
1135
+ }
1136
+ };
1137
+
1138
+ JSSpec.PatternMatcher.prototype.matches = function() {
1139
+ return this.match;
1140
+ };
1141
+
1142
+ JSSpec.PatternMatcher.prototype.explain = function() {
1143
+ return this.explaination;
1144
+ };
1145
+
1146
+ /**
1147
+ * Domain Specific Languages
1148
+ */
1149
+ JSSpec.DSL = {};
1150
+
1151
+ JSSpec.DSL.forString = {
1152
+ normalizeHtml: function() {
1153
+ var html = this;
1154
+
1155
+ // Uniformize quotation, turn tag names and attribute names into lower case
1156
+ html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
1157
+ var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase())
1158
+ return "<" + closingMark + tagName.toLowerCase() + sortedAttrs + ">"
1159
+ });
1160
+
1161
+ // validation self-closing tags
1162
+ html = html.replace(/<(br|hr|img)([^>]*?)>/mg, function(str, tag, attrs) {
1163
+ return "<" + tag + attrs + " />";
1164
+ });
1165
+
1166
+ // append semi-colon at the end of style value
1167
+ html = html.replace(/style="(.*?)"/mg, function(str, styleStr) {
1168
+ styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
1169
+ if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += ";"
1170
+
1171
+ return 'style="' + styleStr + '"'
1172
+ });
1173
+
1174
+ // sort style entries
1175
+
1176
+ // remove empty style attributes
1177
+ html = html.replace(/ style=";"/mg, "");
1178
+
1179
+ // remove new-lines
1180
+ html = html.replace(/\r/mg, '');
1181
+ html = html.replace(/\n/mg, '');
1182
+
1183
+ return html;
1184
+ }
1185
+ };
1186
+
1187
+
1188
+ JSSpec.DSL.describe = function(context, entries, base) {
1189
+ if(base) {
1190
+ for(var i = 0; i < JSSpec.specs.length; i++) {
1191
+ if(JSSpec.specs[i].context === base) {
1192
+ base = JSSpec.specs[i];
1193
+ break;
1194
+ }
1195
+ }
1196
+
1197
+ for(var i = 0; i < base.examples.length; i++) {
1198
+ var example = base.examples[i];
1199
+
1200
+ if(!entries[example.name]) entries[example.name] = example.target;
1201
+ }
1202
+ }
1203
+
1204
+ JSSpec.specs.push(new JSSpec.Spec(context, entries));
1205
+ };
1206
+
1207
+ JSSpec.DSL.value_of = function(target) {
1208
+ if(JSSpec._secondPass) return {};
1209
+
1210
+ var subject = new JSSpec.DSL.Subject(target);
1211
+ return subject;
1212
+ };
1213
+
1214
+ JSSpec.DSL.Subject = function(target) {
1215
+ this.target = target;
1216
+ };
1217
+
1218
+ JSSpec.DSL.Subject.prototype._type = 'Subject';
1219
+
1220
+ JSSpec.DSL.Subject.prototype.should_fail = function(message) {
1221
+ JSSpec._assertionFailure = {message:message};
1222
+ throw JSSpec._assertionFailure;
1223
+ };
1224
+
1225
+ JSSpec.DSL.Subject.prototype.should_be = function(expected) {
1226
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
1227
+ if(!matcher.matches()) {
1228
+ JSSpec._assertionFailure = {message:matcher.explain()};
1229
+ throw JSSpec._assertionFailure;
1230
+ }
1231
+ };
1232
+
1233
+ JSSpec.DSL.Subject.prototype.should_not_be = function(expected) {
1234
+ // TODO JSSpec.EqualityMatcher should support 'condition'
1235
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
1236
+ if(matcher.matches()) {
1237
+ JSSpec._assertionFailure = {message:"'" + this.target + "' should not be '" + expected + "'"};
1238
+ throw JSSpec._assertionFailure;
1239
+ }
1240
+ };
1241
+
1242
+ JSSpec.DSL.Subject.prototype.should_be_empty = function() {
1243
+ this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
1244
+ };
1245
+
1246
+ JSSpec.DSL.Subject.prototype.should_not_be_empty = function() {
1247
+ this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
1248
+ };
1249
+
1250
+ JSSpec.DSL.Subject.prototype.should_be_true = function() {
1251
+ this.should_be(true);
1252
+ };
1253
+
1254
+ JSSpec.DSL.Subject.prototype.should_be_false = function() {
1255
+ this.should_be(false);
1256
+ };
1257
+
1258
+ JSSpec.DSL.Subject.prototype.should_be_null = function() {
1259
+ this.should_be(null);
1260
+ };
1261
+
1262
+ JSSpec.DSL.Subject.prototype.should_be_undefined = function() {
1263
+ this.should_be(undefined);
1264
+ };
1265
+
1266
+ JSSpec.DSL.Subject.prototype.should_not_be_null = function() {
1267
+ this.should_not_be(null);
1268
+ };
1269
+
1270
+ JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() {
1271
+ this.should_not_be(undefined);
1272
+ };
1273
+
1274
+ JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) {
1275
+ var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
1276
+ if(!matcher.matches()) {
1277
+ JSSpec._assertionFailure = {message:matcher.explain()};
1278
+ throw JSSpec._assertionFailure;
1279
+ }
1280
+ };
1281
+
1282
+ JSSpec.DSL.Subject.prototype.should_have = function(num, property) {
1283
+ this._should_have(num, property, "exactly");
1284
+ };
1285
+
1286
+ JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) {
1287
+ this._should_have(num, property, "exactly");
1288
+ };
1289
+
1290
+ JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) {
1291
+ this._should_have(num, property, "at least");
1292
+ };
1293
+
1294
+ JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) {
1295
+ this._should_have(num, property, "at most");
1296
+ };
1297
+
1298
+ JSSpec.DSL.Subject.prototype.should_include = function(expected) {
1299
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
1300
+ if(!matcher.matches()) {
1301
+ JSSpec._assertionFailure = {message:matcher.explain()};
1302
+ throw JSSpec._assertionFailure;
1303
+ }
1304
+ };
1305
+
1306
+ JSSpec.DSL.Subject.prototype.should_not_include = function(expected) {
1307
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
1308
+ if(!matcher.matches()) {
1309
+ JSSpec._assertionFailure = {message:matcher.explain()};
1310
+ throw JSSpec._assertionFailure;
1311
+ }
1312
+ };
1313
+
1314
+ JSSpec.DSL.Subject.prototype.should_match = function(pattern) {
1315
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
1316
+ if(!matcher.matches()) {
1317
+ JSSpec._assertionFailure = {message:matcher.explain()};
1318
+ throw JSSpec._assertionFailure;
1319
+ }
1320
+ }
1321
+ JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) {
1322
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
1323
+ if(!matcher.matches()) {
1324
+ JSSpec._assertionFailure = {message:matcher.explain()};
1325
+ throw JSSpec._assertionFailure;
1326
+ }
1327
+ };
1328
+
1329
+ JSSpec.DSL.Subject.prototype.getType = function() {
1330
+ if(typeof this.target == 'undefined') {
1331
+ return 'undefined';
1332
+ } else if(this.target == null) {
1333
+ return 'null';
1334
+ } else if(this.target._type) {
1335
+ return this.target._type;
1336
+ } else if(JSSpec.util.isDomNode(this.target)) {
1337
+ return 'DomNode';
1338
+ } else {
1339
+ return 'object';
1340
+ }
1341
+ };
1342
+
1343
+ /**
1344
+ * Utilities
1345
+ */
1346
+ JSSpec.util = {
1347
+ escapeTags: function(string) {
1348
+ return string.replace(/</img, '&lt;').replace(/>/img, '&gt;');
1349
+ },
1350
+ escapeMetastring: function(string) {
1351
+ return string.replace(/\r/img, '\\r').replace(/\n/img, '\\n').replace(/\&para\;\<BR\>/img, '\\n').replace(/\t/img, '\\t');
1352
+ },
1353
+ parseOptions: function(defaults) {
1354
+ var options = defaults;
1355
+
1356
+ var url = location.href;
1357
+ var queryIndex = url.indexOf('?');
1358
+ if(queryIndex == -1) return options;
1359
+
1360
+ var query = url.substring(queryIndex + 1).split('#')[0];
1361
+ var pairs = query.split('&');
1362
+ for(var i = 0; i < pairs.length; i++) {
1363
+ var tokens = pairs[i].split('=');
1364
+ options[tokens[0]] = tokens[1];
1365
+ }
1366
+
1367
+ return options;
1368
+ },
1369
+ correctHtmlAttrQuotation: function(html) {
1370
+ html = html.replace(/(\w+)=['"]([^'"]+)['"]/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
1371
+ html = html.replace(/(\w+)=([^ '"]+)/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
1372
+ html = html.replace(/'/mg, '"');
1373
+
1374
+ return html;
1375
+ },
1376
+ sortHtmlAttrs: function(html) {
1377
+ var attrs = [];
1378
+ html.replace(/((\w+)="[^"]+")/mg, function(str, matched) {
1379
+ attrs.push(matched);
1380
+ });
1381
+ return attrs.length == 0 ? "" : " " + attrs.sort().join(" ");
1382
+ },
1383
+ sortStyleEntries: function(styleText) {
1384
+ var entries = styleText.split(/; /);
1385
+ return entries.sort().join("; ");
1386
+ },
1387
+ escapeHtml: function(str) {
1388
+ if(!this._div) {
1389
+ this._div = document.createElement("DIV");
1390
+ this._text = document.createTextNode('');
1391
+ this._div.appendChild(this._text);
1392
+ }
1393
+ this._text.data = str;
1394
+ return this._div.innerHTML;
1395
+ },
1396
+ isDomNode: function(o) {
1397
+ // TODO: make it more stricter
1398
+ return (typeof o.nodeName == 'string') && (typeof o.nodeType == 'number');
1399
+ },
1400
+ inspectDomPath: function(o) {
1401
+ var sb = [];
1402
+ while(o && o.nodeName != '#document' && o.parent) {
1403
+ var siblings = o.parentNode.childNodes;
1404
+ for(var i = 0; i < siblings.length; i++) {
1405
+ if(siblings[i] == o) {
1406
+ sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
1407
+ break;
1408
+ }
1409
+ }
1410
+ o = o.parentNode;
1411
+ }
1412
+ return sb.join(" &gt; ");
1413
+ },
1414
+ inspectDomNode: function(o) {
1415
+ if(o.nodeType == 1) {
1416
+ var nodeName = o.nodeName.toLowerCase();
1417
+ var sb = [];
1418
+ sb.push('<span class="dom_value">');
1419
+ sb.push("&lt;");
1420
+ sb.push(nodeName);
1421
+
1422
+ var attrs = o.attributes;
1423
+ for(var i = 0; i < attrs.length; i++) {
1424
+ if(
1425
+ attrs[i].nodeValue &&
1426
+ attrs[i].nodeName != 'contentEditable' &&
1427
+ attrs[i].nodeName != 'style' &&
1428
+ typeof attrs[i].nodeValue != 'function'
1429
+ ) sb.push(' <span class="dom_attr_name">' + attrs[i].nodeName.toLowerCase() + '</span>=<span class="dom_attr_value">"' + attrs[i].nodeValue + '"</span>');
1430
+ }
1431
+ if(o.style && o.style.cssText) {
1432
+ sb.push(' <span class="dom_attr_name">style</span>=<span class="dom_attr_value">"' + o.style.cssText + '"</span>');
1433
+ }
1434
+ sb.push('&gt;');
1435
+ sb.push(JSSpec.util.escapeHtml(o.innerHTML));
1436
+ sb.push('&lt;/' + nodeName + '&gt;');
1437
+ sb.push(' <span class="dom_path">(' + JSSpec.util.inspectDomPath(o) + ')</span>' );
1438
+ sb.push('</span>');
1439
+ return sb.join("");
1440
+ } else if(o.nodeType == 3) {
1441
+ return '<span class="dom_value">#text ' + o.nodeValue + '</span>';
1442
+ } else {
1443
+ return '<span class="dom_value">UnknownDomNode</span>';
1444
+ }
1445
+ },
1446
+ inspect: function(o, dontEscape, emphasisKey) {
1447
+ var sb, inspected;
1448
+
1449
+ if(typeof o == 'undefined') return '<span class="undefined_value">undefined</span>';
1450
+ if(o == null) return '<span class="null_value">null</span>';
1451
+ if(o._type == 'String') return '<span class="string_value">"' + (dontEscape ? JSSpec.util.escapeMetastring(o) : JSSpec.util.escapeHtml(JSSpec.util.escapeMetastring(o))) + '"</span>';
1452
+
1453
+ if(o._type == 'Date') {
1454
+ return '<span class="date_value">"' + o.toString() + '"</span>';
1455
+ }
1456
+
1457
+ if(o._type == 'Number') return '<span class="number_value">' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '</span>';
1458
+
1459
+ if(o._type == 'Boolean') return '<span class="boolean_value">' + o + '</span>';
1460
+
1461
+ if(o._type == 'RegExp') return '<span class="regexp_value">' + JSSpec.util.escapeHtml(o.toString()) + '</span>';
1462
+
1463
+ if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
1464
+
1465
+ if(o._type == 'Array' || typeof o.length != 'undefined') {
1466
+ sb = [];
1467
+ for(var i = 0; i < o.length; i++) {
1468
+ inspected = JSSpec.util.inspect(o[i]);
1469
+ sb.push(i == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
1470
+ }
1471
+ return '<span class="array_value">[' + sb.join(', ') + ']</span>';
1472
+ }
1473
+
1474
+ // object
1475
+ sb = [];
1476
+ for(var key in o) {
1477
+ if(key == 'should') continue;
1478
+
1479
+ inspected = JSSpec.util.inspect(key) + ":" + JSSpec.util.inspect(o[key]);
1480
+ sb.push(key == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
1481
+ }
1482
+ return '<span class="object_value">{' + sb.join(', ') + '}</span>';
1483
+ }
1484
+ };
1485
+
1486
+ describe = JSSpec.DSL.describe;
1487
+ behavior_of = JSSpec.DSL.describe;
1488
+ value_of = JSSpec.DSL.value_of;
1489
+ expect = JSSpec.DSL.value_of; // @deprecated
1490
+
1491
+ String.prototype._type = "String";
1492
+ Number.prototype._type = "Number";
1493
+ Date.prototype._type = "Date";
1494
+ Array.prototype._type = "Array";
1495
+ Boolean.prototype._type = "Boolean";
1496
+ RegExp.prototype._type = "RegExp";
1497
+
1498
+ var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
1499
+
1500
+ String.prototype.normalizeHtml = JSSpec.DSL.forString.normalizeHtml;
1501
+ String.prototype.asHtml = String.prototype.normalizeHtml; //@deprecated
1502
+ String.prototype.strip = function() {return this.replace(/^\s+/, '').replace(/\s+$/, '');}
1503
+
1504
+
1505
+ /**
1506
+ * Main
1507
+ */
1508
+ JSSpec.defaultOptions = {
1509
+ autorun: 1,
1510
+ specIdBeginsWith: 0,
1511
+ exampleIdBeginsWith: 0,
1512
+ autocollapse: 1
1513
+ };
1514
+ JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
1515
+
1516
+ JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
1517
+ JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
1518
+
1519
+
1520
+
1521
+ window.onload = function() {
1522
+ if(JSSpec.specs.length > 0) {
1523
+ if(!JSSpec.options.inSuite) {
1524
+ JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
1525
+ if(JSSpec.options.rerun) {
1526
+ JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
1527
+ } else {
1528
+ JSSpec.runner.run();
1529
+ }
1530
+ } else {
1531
+ // in suite, send all specs to parent
1532
+ var parentWindow = window.frames.parent.window;
1533
+ for(var i = 0; i < JSSpec.specs.length; i++) {
1534
+ parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
1535
+ }
1536
+ }
1537
+ } else {
1538
+ var links = document.getElementById('list').getElementsByTagName('A');
1539
+ var frameContainer = document.createElement('DIV');
1540
+ frameContainer.style.display = 'none';
1541
+ document.body.appendChild(frameContainer);
1542
+
1543
+ for(var i = 0; i < links.length; i++) {
1544
+ var frame = document.createElement('IFRAME');
1545
+ frame.src = links[i].href + '?inSuite=0&specIdBeginsWith=' + (i * 10000) + '&exampleIdBeginsWith=' + (i * 10000);
1546
+ frameContainer.appendChild(frame);
1547
+ }
1548
+ }
1549
+ }