sproutcore 1.10.0.rc.3 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +8 -8
  2. data/VERSION.yml +1 -1
  3. data/lib/buildtasks/manifest.rake +3 -2
  4. data/lib/frameworks/sproutcore/Buildfile +3 -1
  5. data/lib/frameworks/sproutcore/CHANGELOG.md +26 -2
  6. data/lib/frameworks/sproutcore/apps/showcase/resources/main_page.js +3 -0
  7. data/lib/frameworks/sproutcore/apps/showcase/views/views_item_view.js +1 -1
  8. data/lib/frameworks/sproutcore/apps/welcome/english.lproj/main_page.js +3 -0
  9. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +5 -5
  10. data/lib/frameworks/sproutcore/frameworks/bootstrap/tests/system/browser.js +9 -0
  11. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane_statechart.js +2 -2
  12. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/browser.js +6 -0
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +17 -55
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/animation.js +57 -0
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/isVisible.js +24 -0
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/view_states_test.js +31 -13
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +8 -5
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/animation.js +24 -20
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout.js +39 -29
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/statechart.js +400 -242
  21. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +37 -32
  22. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +1 -8
  23. data/lib/frameworks/sproutcore/frameworks/desktop/panes/modal.js +3 -2
  24. data/lib/frameworks/sproutcore/frameworks/desktop/panes/panel.js +4 -5
  25. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/panel/ui.js +53 -6
  26. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +9 -3
  27. data/lib/frameworks/sproutcore/frameworks/desktop/views/grid.js +1 -1
  28. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +16 -12
  29. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroller.js +16 -2
  30. data/lib/frameworks/sproutcore/frameworks/foundation/mixins/auto_resize.js +10 -8
  31. data/lib/frameworks/sproutcore/frameworks/foundation/tests/mixins/auto_resize_test.js +102 -0
  32. data/lib/frameworks/sproutcore/frameworks/foundation/tests/validators/password.js +15 -7
  33. data/lib/frameworks/sproutcore/frameworks/runtime/system/run_loop.js +2 -2
  34. data/lib/frameworks/sproutcore/frameworks/testing/system/runner.js +4 -0
  35. data/lib/frameworks/sproutcore/phantomjs/minimist.js +181 -0
  36. data/lib/frameworks/sproutcore/phantomjs/q.js +1937 -0
  37. data/lib/frameworks/sproutcore/phantomjs/test_runner.js +733 -0
  38. data/lib/frameworks/sproutcore/themes/ace/resources/button/ace/44px/button.css +2 -2
  39. data/lib/frameworks/sproutcore/themes/ace/resources/button/ace/button.css +11 -11
  40. data/lib/frameworks/sproutcore/themes/ace/resources/button/dark/button.css +5 -5
  41. data/lib/frameworks/sproutcore/themes/ace/resources/button/dark/jumbo/button.css +3 -3
  42. data/lib/frameworks/sproutcore/themes/ace/resources/button/dark/small/button.css +3 -3
  43. data/lib/frameworks/sproutcore/themes/ace/resources/button/popup/select.css +1 -1
  44. data/lib/frameworks/sproutcore/themes/ace/resources/checkbox/ace/checkbox.css +3 -3
  45. data/lib/frameworks/sproutcore/themes/ace/resources/collection/source-list/source_list_view.css +4 -4
  46. data/lib/frameworks/sproutcore/themes/ace/resources/disclosure/ace/disclosure.css +6 -6
  47. data/lib/frameworks/sproutcore/themes/ace/resources/imagebutton/ace/imagebutton.css +5 -5
  48. data/lib/frameworks/sproutcore/themes/ace/resources/menu/menu.css +16 -16
  49. data/lib/frameworks/sproutcore/themes/ace/resources/picker/popover/popover.css +42 -42
  50. data/lib/frameworks/sproutcore/themes/ace/resources/progress/ace/progress.css +19 -19
  51. data/lib/frameworks/sproutcore/themes/ace/resources/radio/radio.css +12 -12
  52. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/horizontal/horizontal.css +7 -7
  53. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/horizontal/horizontal_touch.css +18 -18
  54. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/vertical/vertical.css +8 -8
  55. data/lib/frameworks/sproutcore/themes/ace/resources/scroller/vertical/vertical_touch.css +16 -16
  56. data/lib/frameworks/sproutcore/themes/ace/resources/segmented/44px/segmented.css +2 -2
  57. data/lib/frameworks/sproutcore/themes/ace/resources/segmented/segmented.css +13 -13
  58. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/14px/slider.css +11 -11
  59. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/16px/slider.css +11 -11
  60. data/lib/frameworks/sproutcore/themes/ace/resources/slider/ace/22px/slider.css +11 -11
  61. data/lib/frameworks/sproutcore/themes/ace/resources/split/split.css +6 -6
  62. data/lib/frameworks/sproutcore/themes/ace/resources/toolbar/toolbar.css +2 -2
  63. data/lib/frameworks/sproutcore/themes/iphone_theme/english.lproj/button.css +3 -3
  64. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/button.css +18 -18
  65. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/checkbox.css +7 -7
  66. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/collection.css +2 -2
  67. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/core.css +7 -7
  68. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/disclosure.css +3 -3
  69. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/list_item.css +4 -4
  70. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/menu.css +3 -3
  71. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/menu_item_view.css +5 -5
  72. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/panel.css +7 -7
  73. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/picker.css +3 -3
  74. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/progress.css +5 -5
  75. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/radio.css +4 -4
  76. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/scroller.css +3 -3
  77. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +7 -7
  78. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/slider.css +2 -2
  79. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/split_view.css +2 -2
  80. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/toolbar.css +3 -3
  81. data/lib/sproutcore/tools/server.rb +9 -3
  82. metadata +8 -83
  83. data/lib/frameworks/sproutcore/design/Assorted Images/sproutcore-startup-landscape.jpg +0 -0
  84. data/lib/frameworks/sproutcore/design/Assorted Images/sproutcore-startup-landscape.png +0 -0
  85. data/lib/frameworks/sproutcore/design/Assorted Images/sproutcore-startup-portrait.jpg +0 -0
  86. data/lib/frameworks/sproutcore/design/Assorted Images/sproutcore-startup-portrait.png +0 -0
  87. data/lib/frameworks/sproutcore/design/Assorted Images/sproutcore-startup.png +0 -0
  88. data/lib/frameworks/sproutcore/design/Record State Table.numbers +0 -0
  89. data/lib/frameworks/sproutcore/design/greenhouse-statechart.pdf +0 -0
  90. data/lib/frameworks/sproutcore/tests/phantomjs_runner.phantomjs +0 -611
  91. data/lib/frameworks/sproutcore/themes/ace/designs/dark.png +0 -0
  92. data/lib/frameworks/sproutcore/themes/ace/designs/light.png +0 -0
  93. data/lib/frameworks/sproutcore/themes/ace/designs/psds/panel/PanelPane.opacity +0 -0
  94. data/lib/frameworks/sproutcore/themes/ace/designs/psds/panel/Pointers.opacity +0 -0
  95. data/lib/frameworks/sproutcore/themes/ace/designs/switch/switch_handle.png +0 -0
  96. data/lib/frameworks/sproutcore/themes/ace/designs/switch/switch_off.png +0 -0
  97. data/lib/frameworks/sproutcore/themes/ace/designs/switch/switch_on.png +0 -0
  98. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/10.png +0 -0
  99. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/100.png +0 -0
  100. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/102.png +0 -0
  101. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/110.png +0 -0
  102. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/120.png +0 -0
  103. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/127.png +0 -0
  104. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/18.png +0 -0
  105. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/19.png +0 -0
  106. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/2.png +0 -0
  107. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/24.png +0 -0
  108. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/26.png +0 -0
  109. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/27.png +0 -0
  110. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/28.png +0 -0
  111. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/29.png +0 -0
  112. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/30.png +0 -0
  113. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/31.png +0 -0
  114. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/33.png +0 -0
  115. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/37.png +0 -0
  116. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/41.png +0 -0
  117. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/16/99.png +0 -0
  118. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/10.png +0 -0
  119. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/100.png +0 -0
  120. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/102.png +0 -0
  121. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/110.png +0 -0
  122. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/120.png +0 -0
  123. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/127.png +0 -0
  124. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/18.png +0 -0
  125. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/19.png +0 -0
  126. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/2.png +0 -0
  127. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/24.png +0 -0
  128. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/26.png +0 -0
  129. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/27.png +0 -0
  130. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/28.png +0 -0
  131. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/29.png +0 -0
  132. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/30.png +0 -0
  133. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/31.png +0 -0
  134. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/33.png +0 -0
  135. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/37.png +0 -0
  136. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/41.png +0 -0
  137. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/24/99.png +0 -0
  138. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/10.png +0 -0
  139. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/100.png +0 -0
  140. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/102.png +0 -0
  141. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/110.png +0 -0
  142. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/120.png +0 -0
  143. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/127.png +0 -0
  144. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/18.png +0 -0
  145. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/19.png +0 -0
  146. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/2.png +0 -0
  147. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/24.png +0 -0
  148. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/26.png +0 -0
  149. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/27.png +0 -0
  150. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/28.png +0 -0
  151. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/29.png +0 -0
  152. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/30.png +0 -0
  153. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/31.png +0 -0
  154. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/33.png +0 -0
  155. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/37.png +0 -0
  156. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/41.png +0 -0
  157. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/32/99.png +0 -0
  158. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/48/10.png +0 -0
  159. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/48/18.png +0 -0
  160. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/48/19.png +0 -0
  161. data/lib/frameworks/sproutcore/themes/legacy_theme/Source/icons/48/2.png +0 -0
@@ -0,0 +1,733 @@
1
+ /**
2
+ PhantomJS Unit Test Runner
3
+
4
+ This script can be used to run the SproutCore unit tests using PhantomJS.
5
+ It requires that sc-server is running and uses the special /sc/targets.json
6
+ url provided by abbot to get the list of unit tests to run.
7
+ Because of this, it can also be used to run unit tests for any SproutCore app
8
+ as well.
9
+
10
+ Run like:
11
+ phantomjs test_runner.js
12
+
13
+ Use -h to get help.
14
+ */
15
+
16
+ /*globals require, phantom, console */
17
+
18
+ // HACK: rename require so abbot does not warn about it
19
+ var require_module = require;
20
+
21
+ // require modules
22
+ var system = require_module('system'),
23
+ webpage = require_module('webpage'),
24
+ minimist = require_module('./minimist'),
25
+ Q = require_module('./q');
26
+
27
+ // constants
28
+ var EXIT_SUCCESS = 0,
29
+ EXIT_FAILURE = 1,
30
+ EXIT_ERROR = 2,
31
+ PASSED = 'passed',
32
+ FAILED = 'failed',
33
+ ERRORS = "errors",
34
+ WARNINGS = "warnings",
35
+ TIMEOUT = 'timeout',
36
+ SKIPPED = 'skipped',
37
+ PASS_COLOR = '\x1b[32m',
38
+ FAIL_COLOR = '\x1b[31m',
39
+ ERROR_COLOR = '\x1b[41m',
40
+ WARN_COLOR = '\x1b[33m',
41
+ TIMEOUT_COLOR = '\x1b[36m',
42
+ SKIPPED_COLOR = '\x1b[46m',
43
+ RESET_COLOR = '\x1b[0m',
44
+ TIMEOUT_WAIT = 30000;
45
+
46
+ // vars
47
+ var args = processArgs(system.args.slice(1), system.env),
48
+ urlRoot = 'http://' + args.host + ':' + args.port;
49
+
50
+ /**
51
+ Processes command line arguments.
52
+
53
+ Uses corresponding environment variables as the default values,
54
+ allowing override by the command line arguments.
55
+
56
+ @param {Array.<string>} args Command line arguments (minus argv[0])
57
+ @param {Object} env Environment variables
58
+ @returns {{ travis: boolean, host: string, port: number, includeTargets: ?Array, excludeTargets: ?Array,
59
+ filter: ?RegExp, experimental: boolean, verbose: boolean, veryVerbose: boolean, help: boolean }}
60
+ */
61
+ function processArgs(args, env) {
62
+ args = minimist(args, {
63
+ default: {
64
+ travis: !!env.TRAVIS,
65
+ host: env.HOST || 'localhost',
66
+ port: env.PORT || 4020,
67
+ includeTargets: null,
68
+ excludeTargets: null,
69
+ targetKinds: null,
70
+ filter: null,
71
+ experimental: true,
72
+ verbose: !!env.VERBOSE && !env.TRAVIS,
73
+ veryVerbose: !!env.VERY_VERBOSE,
74
+ help: false
75
+ },
76
+ alias: {
77
+ includeTargets: 'include-targets',
78
+ excludeTargets: 'exclude-targets',
79
+ targetKinds: 'target-kinds',
80
+ verbose: 'v',
81
+ veryVerbose: ['V', 'very-verbose'],
82
+ help: 'h'
83
+ }
84
+ });
85
+
86
+ if (typeof args.includeTargets === 'string') {
87
+ args.includeTargets = args.includeTargets.split(',');
88
+ }
89
+ if (typeof args.excludeTargets === 'string') {
90
+ args.excludeTargets = args.excludeTargets.split(',');
91
+ }
92
+ if (typeof args.targetKinds === 'string') {
93
+ args.targetKinds = args.targetKinds.split(',');
94
+ }
95
+ if (typeof args.filter === 'string') {
96
+ args.filter = new RegExp(args.filter);
97
+ }
98
+ args.verbose = args.verbose || args.veryVerbose;
99
+
100
+ if (args.includeTargets && args.excludeTargets) {
101
+ throw new Error('Cannot whitelist and blacklist targets at the same time');
102
+ }
103
+
104
+ return args;
105
+ }
106
+
107
+ /**
108
+ Logs a summary of the test results.
109
+
110
+ Number of tests run out of total number of test is logged (some tests may not run because they were skipped).
111
+ Number of tests that failed, had errors, had warnings, or timed out is logged.
112
+
113
+ @param {Array} allResults The results object for each test (including skipped tests).
114
+ @returns {number} Final result of the test run. Will be 0 if all tests passed, otherwise 1, so it can be passed
115
+ directly to phantom.exit().
116
+ */
117
+ function logSummary(allResults) {
118
+ var ran = 0,
119
+ passed = 0,
120
+ failed = 0,
121
+ errored = 0,
122
+ warned = 0,
123
+ timedout = 0,
124
+ skipped = 0,
125
+ parts;
126
+
127
+ allResults.forEach(function (results) {
128
+ if (results.result === PASSED) {
129
+ passed++;
130
+ } else if (results.result === FAILED) {
131
+ failed++;
132
+ } else if (results.result === ERRORS) {
133
+ errored++;
134
+ } else if (results.result === WARNINGS) {
135
+ warned++;
136
+ } else if (results.result === TIMEOUT) {
137
+ timedout++;
138
+ } else if (results.result === SKIPPED) {
139
+ skipped++;
140
+ }
141
+
142
+ if (results.result !== SKIPPED) {
143
+ ran++;
144
+ }
145
+ });
146
+
147
+ parts = [
148
+ '\nRan ', ran, ' of ', allResults.length + ' tests.'
149
+ ];
150
+
151
+ if (failed > 0) {
152
+ parts.push(' ');
153
+ parts.push(failed);
154
+ parts.push(' failed.');
155
+ }
156
+
157
+ if (errored > 0) {
158
+ parts.push(' ');
159
+ parts.push(errored);
160
+ parts.push(' had errors.');
161
+ }
162
+
163
+ if (warned > 0) {
164
+ parts.push(' ');
165
+ parts.push(warned);
166
+ parts.push(' had warnings.');
167
+ }
168
+
169
+ if (timedout > 0) {
170
+ parts.push(' ');
171
+ parts.push(timedout);
172
+ parts.push(' timed out.');
173
+ }
174
+
175
+ console.log(parts.join(''));
176
+
177
+ allResults.forEach(function (results) {
178
+ if (results.result !== PASSED && results.result !== SKIPPED) {
179
+ logTestResult(results, results.test);
180
+ }
181
+ });
182
+
183
+ return (failed + errored + timedout > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
184
+ }
185
+
186
+ /**
187
+ Filters targets.
188
+
189
+ If includeTargets or excludeTargets arguments were given to the script,
190
+ they will be used to do the filtering.
191
+ If includeTargets was specified, only those targets will be included.
192
+ If excludeTargets was specified, all targets except those targets will be included.
193
+ As a shortcut, the --no-experimental argument will filter out the /sproutcore/experimental
194
+ frameworks.
195
+
196
+ If a target is filtered out, it will be completely ignored. Its tests will not be considered
197
+ when showing total tests and they will not be logged as skipped tests.
198
+
199
+ @param {Object} target Target object to filter
200
+ @returns {boolean} false if this target should be filtered out
201
+ */
202
+ function filterTarget(target) {
203
+ var include = true;
204
+
205
+ if (args.targetKinds && args.targetKinds.indexOf(target.kind) < 0) {
206
+ include = false;
207
+ }
208
+ if (args.excludeTargets && args.excludeTargets.indexOf(target.name) >= 0) {
209
+ include = false;
210
+ }
211
+ if (args.includeTargets && args.includeTargets.indexOf(target.name) < 0) {
212
+ include = false;
213
+ }
214
+ if (/^\/sproutcore\/experimental/.test(target.name) && !args.experimental) {
215
+ include = false;
216
+ }
217
+
218
+ return include;
219
+ }
220
+
221
+ /**
222
+ Filters tests.
223
+
224
+ If a filter argument was given to the script, it will be used to do the filtering.
225
+ The url of the test as returned by abbot will be tested against the filter regexp.
226
+
227
+ If a test is filtered out, it will be considered a skipped test. It will be considered
228
+ when showing total tests, and will be logged as a skipped test.
229
+
230
+ @param {Object} test Test object to filter
231
+ @returns {boolean} false if this test should be filtered out
232
+ */
233
+ function filterTest(test) {
234
+ return args.filter ? args.filter.test(test.url) : true;
235
+ }
236
+
237
+ /**
238
+ Determines the text color for the given result.
239
+
240
+ @param {string} result Result string
241
+ @returns {string} ANSI escape code required to change
242
+ the text to the correct color for the test result
243
+ */
244
+ function colorForResult(result) {
245
+ var color;
246
+
247
+ switch (result) {
248
+ case PASSED:
249
+ color = PASS_COLOR;
250
+ break;
251
+ case FAILED:
252
+ color = FAIL_COLOR;
253
+ break;
254
+ case ERRORS:
255
+ color = ERROR_COLOR;
256
+ break;
257
+ case WARNINGS:
258
+ color = WARN_COLOR;
259
+ break;
260
+ case TIMEOUT:
261
+ color = TIMEOUT_COLOR;
262
+ break;
263
+ case SKIPPED:
264
+ color = SKIPPED_COLOR;
265
+ break;
266
+ default:
267
+ color = '';
268
+ break;
269
+ }
270
+
271
+ return color;
272
+ }
273
+
274
+ /**
275
+ Determines what result the test should have based on the result of its assertions.
276
+
277
+ @param {Object} results Results object for the test
278
+ @returns {string} String describing the test result
279
+ */
280
+ function getTestResult(results) {
281
+ var testResult;
282
+
283
+ if (results.isSkipped) {
284
+ testResult = SKIPPED;
285
+ } else if (results.isTimeout) {
286
+ testResult = TIMEOUT;
287
+ } else if (results.passed === results.total) {
288
+ testResult = PASSED;
289
+ } else if (results.errors > 0) {
290
+ testResult = ERRORS;
291
+ } else if (results.failed > 0) {
292
+ testResult = FAILED;
293
+ } else if (results.warnings > 0) {
294
+ testResult = WARNINGS;
295
+ }
296
+
297
+ return testResult;
298
+ }
299
+
300
+ /**
301
+ * Logs test assertions.
302
+ *
303
+ * If the verbose option is used, logs all test assertions, regardless of result.
304
+ * Otherwise, only logs failed test assertions.
305
+ *
306
+ * @param {Object} results Test results object
307
+ * @param {string} testResult Test result string
308
+ */
309
+ function logTestAssertions(results, testResult) {
310
+ var assertionsByTest;
311
+ if (args.verbose || testResult === FAILED || testResult === ERRORS) {
312
+ assertionsByTest = {};
313
+
314
+ results.assertions.forEach(function (assertion) {
315
+ var parts = assertion.module.split('\n'),
316
+ testId = parts[1] + ' module: ' + assertion.test;
317
+ assertionsByTest[testId] = assertionsByTest[testId] || [];
318
+ assertionsByTest[testId].push(assertion);
319
+ });
320
+
321
+ Object.keys(assertionsByTest).forEach(function (testId) {
322
+ var assertions = assertionsByTest[testId],
323
+ firstFail = true;
324
+
325
+ assertions.forEach(function (assertion) {
326
+ var parts,
327
+ color;
328
+
329
+ if (args.verbose || assertion.result !== PASSED) {
330
+ if (firstFail) {
331
+ console.log(testId);
332
+ firstFail = false;
333
+ }
334
+
335
+ color = colorForResult(assertion.result);
336
+ parts = [
337
+ ' ', assertion.message, ': ', color, assertion.result, RESET_COLOR
338
+ ];
339
+
340
+ console.log(parts.join(''));
341
+ }
342
+ });
343
+ });
344
+ }
345
+ }
346
+
347
+ /**
348
+ Logs the result of the test.
349
+
350
+ @param {Object} results Results object for the test
351
+ @param {Object} test Test object
352
+ */
353
+ function logTestResult(results, test) {
354
+ var testResult = getTestResult(results),
355
+ testResultColor = colorForResult(testResult),
356
+ parts;
357
+
358
+ results.result = testResult;
359
+
360
+ parts = [
361
+ '[', test.index + 1, '/', test.totalTests, ']',
362
+ ' ', testResultColor, test.url,
363
+ ' (', testResult, ')', RESET_COLOR
364
+ ];
365
+
366
+ // Check that the test page actually ran (wasn't skipped, didn't timeout).
367
+ if (results.tests > 0) {
368
+ parts.push(': Completed ');
369
+ parts.push(results.tests);
370
+ parts.push(' tests in ');
371
+ parts.push(results.runtime);
372
+ parts.push(' msecs.');
373
+ parts.push(' ');
374
+ parts.push(results.total);
375
+ parts.push(' total assertions:');
376
+
377
+ if (results.passed > 0) {
378
+ parts.push(' ');
379
+ parts.push(PASS_COLOR);
380
+ parts.push(results.passed);
381
+ parts.push(' passed');
382
+ parts.push(RESET_COLOR);
383
+ }
384
+
385
+ if (results.failed > 0) {
386
+ parts.push(' ');
387
+ parts.push(FAIL_COLOR);
388
+ parts.push(results.failed);
389
+ parts.push(' failed');
390
+ parts.push(RESET_COLOR);
391
+ }
392
+
393
+ if (results.errors > 0) {
394
+ parts.push(' ');
395
+ parts.push(ERROR_COLOR);
396
+ parts.push(results.errors);
397
+ parts.push(' errors');
398
+ parts.push(RESET_COLOR);
399
+ }
400
+
401
+ if (results.warnings > 0) {
402
+ parts.push(' ');
403
+ parts.push(WARN_COLOR);
404
+ parts.push(results.warnings);
405
+ parts.push(' warnings');
406
+ parts.push(RESET_COLOR);
407
+ }
408
+ }
409
+
410
+ // Add a note if there were unhandled exceptions on the page.
411
+ // Any exceptions inside a test() are caught by the test runner,
412
+ // so an unhandled exception must have happened either on page
413
+ // load, or inside the test runner code. Either way, it's
414
+ // bad news!
415
+ if (results.hadUnhandledError) {
416
+ parts.push(' ');
417
+ parts.push(ERROR_COLOR);
418
+ parts.push('(');
419
+ parts.push('with unhandled errors');
420
+ parts.push(')');
421
+ parts.push(RESET_COLOR);
422
+ }
423
+
424
+ console.log(parts.join(''));
425
+ logTestAssertions(results, testResult);
426
+ }
427
+
428
+ /**
429
+ Configures the unit test page.
430
+
431
+ Callbacks will be set up so that we can determine:
432
+ * The result of the test.
433
+ * If any unhandled exceptions occurred during the test.
434
+ * If the test takes too long to run (possibly due to an unhandled exception).
435
+
436
+ @param {WebPage} page PhantomJS page for the unit test
437
+ @param {Q.defer} deferred Deferred object representing the result of the test
438
+ @param {Object} test Test object
439
+ */
440
+ function configureTestPage(page, deferred, test) {
441
+ var timeoutId;
442
+
443
+ // Set a reasonable viewport size.
444
+ page.viewportSize = { width: 1024, height: 768 };
445
+
446
+ // When the unit test notifies us that it is complete,
447
+ // resolve the promise and log the result.
448
+ page.onCallback = function (results) {
449
+ // Make sure we haven't already resolved this test.
450
+ if (deferred.promise.isPending()) {
451
+ page.close();
452
+ results.test = test;
453
+ clearTimeout(timeoutId);
454
+ results.hadUnhandledError = !!test.hadUnhandledError;
455
+ logTestResult(results, test);
456
+ deferred.resolve(results);
457
+ }
458
+ };
459
+
460
+ // If an unhandled exception or some other error occurs,
461
+ // the test page may never invoke callPhantom. We want to
462
+ // make sure we don't wait forever, so set a timeout.
463
+ // If we hit this, we will resolve the promise as a timeout.
464
+ timeoutId = setTimeout(function () {
465
+ var results;
466
+
467
+ // If not resolved yet, resolve as a timeout, possibly due to an
468
+ // unhandled exception.
469
+ if (deferred.promise.isPending()) {
470
+ page.close();
471
+
472
+ results = {
473
+ isTimeout: true,
474
+ hadUnhandledError: !!test.hadUnhandledError,
475
+ test: test
476
+ };
477
+
478
+ logTestResult(results, test);
479
+ deferred.resolve(results);
480
+ }
481
+ }, TIMEOUT_WAIT);
482
+
483
+ page.onError = function (msg, trace) {
484
+ // If very verbose, dump console messages to the console.
485
+ if (args.veryVerbose) {
486
+ console.log('ERROR: ' + msg);
487
+ }
488
+ // Indicate that an error occurred. This may or may not cause the unit
489
+ // test to fail to complete. We will not resolve the promise right now.
490
+ // Instead, wait to see if the script finished, otherwise the timeout
491
+ // will handle it.
492
+ test.hadUnhandledError = true;
493
+ };
494
+
495
+ if (args.veryVerbose) {
496
+ // If very verbose, dump console messages to the console.
497
+ page.onConsoleMessage = function (msg) {
498
+ console.log('CONSOLE: ' + msg);
499
+ };
500
+ }
501
+ }
502
+
503
+ /**
504
+ Start running a single unit test.
505
+
506
+ Returns a promise which will be resolved when the unit test completes.
507
+ If the test has been filtered out, the promise will be immediately
508
+ resolved.
509
+
510
+ @param {Object} test Test object
511
+ @returns {Q.promise} Promise which will resolve upon test completion
512
+ */
513
+ function runTest(test) {
514
+ var url,
515
+ page,
516
+ results,
517
+ deferred = Q.defer();
518
+
519
+ // Check if we should run this test.
520
+ if (filterTest(test)) {
521
+ url = urlRoot + test.url;
522
+ page = webpage.create();
523
+ configureTestPage(page, deferred, test);
524
+
525
+ page.open(url, function (status) {
526
+ if (status === 'fail') {
527
+ // Page failed to load, reject the promise.
528
+ page.close();
529
+ deferred.reject(new Error('Could not open page: ' + url));
530
+ }
531
+ });
532
+ } else {
533
+ // Resolve as a skipped test.
534
+ results = {
535
+ isSkipped: true,
536
+ test: test
537
+ };
538
+ logTestResult(results, test);
539
+ deferred.resolve(results);
540
+ }
541
+
542
+ return deferred.promise;
543
+ }
544
+
545
+ /**
546
+ Runs all the unit tests.
547
+
548
+ Returns a single promise which will resolve when all unit tests have completed.
549
+ This function is a little complex because we can't fire off all the tests in
550
+ parallel. Doing so causes PhantomJS to run out of resources. So the
551
+ promises need to be chained in sequence.
552
+
553
+ @param {Array} tests Test object for all the unit tests
554
+ @returns {Q.promise} Promise which will resolve when all tests finish
555
+ */
556
+ function runTests(tests) {
557
+ var len = tests.length;
558
+
559
+ // Reduce the tests down to a single promise.
560
+ return tests.reduce(function (p, test, idx) {
561
+ // Set the index/length for the test, so we can log progress.
562
+ test.index = idx;
563
+ test.totalTests = len;
564
+
565
+ // Chain a new promise...
566
+ return p.then(function (allResults) {
567
+ // that returns the promise from running the test...
568
+ return runTest(test).then(function (results) {
569
+ // that pushes its result onto the list of test results.
570
+ allResults.push(results);
571
+ return allResults;
572
+ });
573
+ });
574
+ }, Q([]));
575
+ }
576
+
577
+ /**
578
+ Fetches the tests for the given target.
579
+
580
+ Follows the target's link_tests path to get the JSON list of
581
+ test urls for the target.
582
+ Returns a promise that resolves when the JSON page finishes loading
583
+ and we get our list of tests.
584
+
585
+ @param {Object} target Target object
586
+ @returns {Q.promise} Promise which will resolve when we get the
587
+ list of test for the target
588
+ */
589
+ function fetchTestsForTarget(target) {
590
+ var url = urlRoot + target.link_tests,
591
+ page = webpage.create(),
592
+ deferred = Q.defer();
593
+
594
+ page.open(url, function (status) {
595
+ var tests;
596
+
597
+ if (status === 'success') {
598
+ tests = this.evaluate(function () {
599
+ return JSON.parse(document.getElementsByTagName('pre')[0].innerHTML);
600
+ });
601
+ page.close();
602
+
603
+ if (tests) {
604
+ deferred.resolve(tests);
605
+ } else {
606
+ deferred.reject(new Error('Could not find tests'));
607
+ }
608
+ } else {
609
+ deferred.reject(new Error('Could not open page: ' + url));
610
+ }
611
+ });
612
+
613
+ return deferred.promise;
614
+ }
615
+
616
+ /**
617
+ Fetches the tests for all targets.
618
+
619
+ Returns a single promise which will resolve when all the tests for all targets
620
+ have been fetched.
621
+
622
+ @param {Array} targets Target objects for all the targets
623
+ @returns {Q.promise} Promise which will resolve when all test for
624
+ all targets have been fetched
625
+ */
626
+ function fetchTests(targets) {
627
+ // Reduce the targets down to a single promise.
628
+ return targets.reduce(function (p, target) {
629
+ // Check if we should handle this target.
630
+ if (filterTarget(target)) {
631
+ // Chain a new promise...
632
+ return p.then(function (allTests) {
633
+ // that returns the promise from fetching the tests...
634
+ return fetchTestsForTarget(target).then(function (tests) {
635
+ // that pushes its result onto the list of tests.
636
+ return allTests.concat(tests);
637
+ });
638
+ });
639
+ } else {
640
+ // If we filter out the target, we completely ignore it.
641
+ // So just return the previous promise, don't chain anything
642
+ // extra for this target.
643
+ return p;
644
+ }
645
+ }, Q([]));
646
+ }
647
+
648
+ /**
649
+ Fetches the list of targets.
650
+
651
+ Uses the special /sc/targets.json abbot url to get the JSON list of targets.
652
+ Returns a promise that resolves when the JSON page finishes loading
653
+ and we get our list of targets.
654
+
655
+ @returns {Q.promise} Promise which will resolve when we get the list of targets
656
+ */
657
+ function fetchTargets() {
658
+ var url = urlRoot + '/sc/targets.json',
659
+ page = webpage.create(),
660
+ deferred = Q.defer();
661
+
662
+ page.open(url, function (status) {
663
+ var targets;
664
+
665
+ if (status === 'success') {
666
+ targets = this.evaluate(function () {
667
+ return JSON.parse(document.getElementsByTagName('pre')[0].innerHTML);
668
+ });
669
+ page.close();
670
+
671
+ if (targets) {
672
+ deferred.resolve(targets);
673
+ } else {
674
+ deferred.reject(new Error('Could not find targets'));
675
+ }
676
+ } else {
677
+ deferred.reject(new Error('Could not open page: ' + url));
678
+ }
679
+ });
680
+
681
+ return deferred.promise;
682
+ }
683
+
684
+ /**
685
+ Main entry point to the test runner.
686
+
687
+ Handles the following steps:
688
+ * Fetches the targets.
689
+ * Fetches the tests for those targets (minus any filtered targets).
690
+ * Runs the tests (including logging the result of each test).
691
+ * Logs a summary of the test results.
692
+
693
+ @returns {Q.promise} Promise that resolves when the test runner finishes.
694
+ */
695
+ function run() {
696
+ return Q.fcall(fetchTargets)
697
+ .then(fetchTests)
698
+ .then(runTests)
699
+ .then(logSummary);
700
+ }
701
+
702
+ if (!args.help) {
703
+ // Run the test runner.
704
+ // Exit phantom with an exit status indicating if the tests passed or failed.
705
+ run()
706
+ .then(function (finalResult) {
707
+ phantom.exit(finalResult);
708
+ }, function (reason) {
709
+ console.log(reason);
710
+ phantom.exit(EXIT_ERROR);
711
+ });
712
+ } else {
713
+ console.log('SproutCore PhantomJS Test Runner');
714
+ console.log('');
715
+ console.log(' Runs unit tests under PhantomJS. Requires sc-server to be running.');
716
+ console.log(' Options below, command line options override environment variables.');
717
+ console.log('');
718
+ console.log('Options:');
719
+ console.log('');
720
+ console.log(' --host, env[HOST] sc-server host [localhost]');
721
+ console.log(' --port, env[PORT] sc-server port [4020]');
722
+ console.log(' --include-targets Comma-delimited list of targets to include');
723
+ console.log(' --exclude-targets Comma-delimited list of targets to exclude');
724
+ console.log(' --target-kinds Comma-delimited list of target kinds to include');
725
+ console.log(' --filter Regular expression to use to filter tests');
726
+ console.log(' --[no-]experimetal Shortcut to control inclusion of experimental framework tests [true]');
727
+ console.log(' --travis, env[TRAVIS] Running under Travis CI [false]');
728
+ console.log(' -v, --verbose, env[VERBOSE] Log test assertion results [false]');
729
+ console.log(' -V, --very-verbose, env[VERY_VERBOSE] Log test page console messages [false]');
730
+ console.log(' -h, --help This help page');
731
+
732
+ phantom.exit(EXIT_SUCCESS);
733
+ }