benchmark runner
Thomas Aylott committed
Dec 17, 2013 at 00:25 UTC
91821007ed640c0cfcba6b3c9d179c5bd11c4a50
32 files changed
+1568
-182
.travis.yml
+2
@@ -26,6 +26,7 @@ env:
26
matrix:
27
- TEST_TYPE=test:full
28
- TEST_TYPE=lint
29
+ - TEST_TYPE=perf:full
30
- TEST_TYPE=test:coverage
31
- TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
32
- TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
@@ -43,6 +44,7 @@ matrix:
44
allow_failures:
45
- env: TEST_TYPE=lint
46
- env: TEST_TYPE=test:coverage
47
+ - env: TEST_TYPE=perf:full
48
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
49
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
50
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie9
Gruntfile.js
+35
-1
@@ -21,7 +21,8 @@ module.exports = function(grunt) {
21
browserify: require('./grunt/config/browserify'),
22
populist: require('./grunt/config/populist'),
23
connect: require('./grunt/config/server')(grunt),
24
- "webdriver-jasmine": require('./grunt/config/webdriver-jasmine.js'),
24
+ "webdriver-jasmine": require('./grunt/config/webdriver-jasmine'),
25
+ "webdriver-perf": require('./grunt/config/webdriver-perf'),
26
npm: require('./grunt/config/npm'),
27
clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js', '.module-cache'],
28
jshint: require('./grunt/config/jshint'),
@@ -39,6 +40,8 @@ module.exports = function(grunt) {
40
// Alias 'jshint' to 'lint' to better match the workflow we know
41
grunt.registerTask('lint', ['jshint']);
42
43
+ grunt.registerTask('download-previous-version', require('./grunt/tasks/download-previous-version.js'));
44
+
45
// Register jsx:debug and :release tasks.
46
grunt.registerMultiTask('jsx', jsxTask);
47
@@ -51,6 +54,8 @@ module.exports = function(grunt) {
54
55
grunt.registerMultiTask('webdriver-jasmine', webdriverJasmineTasks);
56
57
+ grunt.registerMultiTask('webdriver-perf', require('./grunt/tasks/webdriver-perf'));
58
+
59
grunt.registerMultiTask('npm', npmTask);
60
61
grunt.registerTask('npm-react:release', npmReactTasks.buildRelease);
@@ -67,6 +72,14 @@ module.exports = function(grunt) {
72
'version-check',
73
'browserify:withCodeCoverageLogging'
74
]);
75
+ grunt.registerTask('build:perf', [
76
+ 'jsx:release',
77
+ 'version-check',
78
+ 'browserify:transformer',
79
+ 'browserify:basic',
80
+ 'browserify:min',
81
+ 'download-previous-version'
82
+ ]);
83
grunt.registerTask('build:test', [
84
'jsx:test',
85
'version-check',
@@ -84,6 +97,12 @@ module.exports = function(grunt) {
97
'webdriver-jasmine:local'
98
]);
99
100
+ grunt.registerTask('perf:webdriver:phantomjs', [
101
+ 'connect',
102
+ 'webdriver-phantomjs',
103
+ 'webdriver-perf:local'
104
+ ]);
105
+
106
grunt.registerTask('test:full', [
107
'build:test',
108
'build:basic',
@@ -98,6 +117,20 @@ module.exports = function(grunt) {
117
'webdriver-jasmine:saucelabs_chrome'
118
]);
119
120
+ grunt.registerTask('perf:full', [
121
+ 'build:perf',
122
+
123
+ 'connect',
124
+ 'webdriver-phantomjs',
125
+ 'webdriver-perf:local',
126
+
127
+ 'sauce-tunnel',
128
+ 'webdriver-perf:saucelabs_firefox',
129
+ 'webdriver-perf:saucelabs_chrome',
130
+ 'webdriver-perf:saucelabs_ie11',
131
+ 'webdriver-perf:saucelabs_ie8',
132
+ ]);
133
+
134
grunt.registerTask('test:webdriver:saucelabs', [
135
'build:test',
136
'build:basic',
@@ -137,6 +170,7 @@ module.exports = function(grunt) {
170
'coverage:parse'
171
]);
172
grunt.registerTask('test', ['build:test', 'build:basic', 'test:webdriver:phantomjs']);
173
+ grunt.registerTask('perf', ['build:perf', 'perf:webdriver:phantomjs']);
174
grunt.registerTask('npm:test', ['build', 'npm:pack']);
175
176
// Optimized build task that does all of our builds. The subtasks will be run
grunt/config/server.js
+2
@@ -44,6 +44,8 @@ module.exports = function(grunt){
44
coverageWriteStream.write(log.message + '\n');
45
} else if (log.type == 'coverage done') {
46
grunt.task.run('finalize-coverage-stream');
47
+ } else if (log.type == 'perf') {
48
+ grunt.event.emit('perf results', log.message);
49
} else {
50
grunt.verbose.writeln(log);
51
}
grunt/config/webdriver-all.js
new
+113
@@ -0,0 +1,113 @@
1
+'use strict';
2
+
3
+var grunt = require('grunt');
4
+
5
+module.exports = function(props){
6
+ if (typeof props.url != 'string') {
7
+ throw TypeError('expected url string');
8
+ }
9
+ if ('isDoneTimeout' in props && typeof props.isDoneTimeout != 'number') {
10
+ throw TypeError('expected isDoneTimeout to be a number');
11
+ }
12
+ if ('onStart' in props && typeof props.onStart != 'function') {
13
+ throw TypeError('expected onStart to be a function');
14
+ }
15
+ if ('onComplete' in props && typeof props.onComplete != 'function') {
16
+ throw TypeError('expected onComplete to be a function');
17
+ }
18
+ if ('onError' in props && typeof props.onError != 'function') {
19
+ throw TypeError('expected onError to be a function');
20
+ }
21
+
22
+ var exports = {};
23
+
24
+ exports.local = {
25
+ webdriver: {
26
+ remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
27
+ },
28
+ url: props.url,
29
+ onStart: props.onStart,
30
+ onComplete: props.onComplete,
31
+ onError: props.onError,
32
+ isDoneTimeout: props.isDoneTimeout
33
+ };
34
+
35
+ if (grunt.option('debug')) {
36
+ exports.local.url += (exports.local.url.indexOf('?') == -1 ? '?' : '&') + 'debug=' + grunt.option('debug');
37
+ }
38
+
39
+ exports.saucelabs = {
40
+ webdriver: {
41
+ remote: {
42
+ /* https://github.com/admc/wd/blob/master/README.md#named-parameters */
43
+ user: process.env.SAUCE_USERNAME || 'React',
44
+ pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
45
+ protocol: 'http:',
46
+ hostname: 'ondemand.saucelabs.com',
47
+ port: '80',
48
+ path: '/wd/hub'
49
+ }
50
+ },
51
+ desiredCapabilities: {
52
+ "build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
53
+ "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
54
+ "browserName": "chrome"
55
+ },
56
+ url: exports.local.url,
57
+ onStart: function(browser){
58
+ grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
59
+ if (props.onStart) {
60
+ return props.onStart(browser);
61
+ }
62
+ },
63
+ onComplete: exports.local.onComplete,
64
+ onError: exports.local.onError,
65
+ isDoneTimeout: exports.local.isDoneTimeout
66
+ };
67
+
68
+ /* https://saucelabs.com/platforms */
69
+ exports.saucelabs_ios =
70
+ exports.saucelabs_ios7 = sauceItUp({ browserName: 'iphone', version: '7', platform:'OS X 10.9' });
71
+ exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
72
+ exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
73
+ exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
74
+ exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
75
+ exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
76
+
77
+ exports.saucelabs_ipad =
78
+ exports.saucelabs_ipad7 = sauceItUp({ browserName: 'ipad', version: '7', platform:'OS X 10.9' });
79
+ exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
80
+ exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
81
+ exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
82
+ exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
83
+ exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
84
+
85
+ exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
86
+ exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
87
+
88
+ exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
89
+ exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
90
+ exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
91
+
92
+ exports.saucelabs_ie =
93
+ exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
94
+ exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
95
+ exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
96
+ exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
97
+
98
+ function sauceItUp(desiredCapabilities) {
99
+ desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
100
+ desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
101
+ return {
102
+ webdriver: exports.saucelabs.webdriver,
103
+ url: exports.saucelabs.url,
104
+ onStart: exports.saucelabs.onStart,
105
+ onComplete: exports.saucelabs.onComplete,
106
+ onError: exports.saucelabs.onError,
107
+ isDoneTimeout: exports.saucelabs.isDoneTimeout,
108
+ desiredCapabilities: desiredCapabilities,
109
+ };
110
+ }
111
+
112
+ return exports;
113
+}
grunt/config/webdriver-jasmine.js
+2
-78
@@ -2,11 +2,7 @@
2
3
var grunt = require('grunt');
4
5
-
6
-exports.local = {
7
- webdriver: {
8
- remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
9
- },
5
+module.exports = require('./webdriver-all')({
6
url: "http://127.0.0.1:9999/test/index.html",
7
onComplete: function(passed){
8
if (!passed){
@@ -16,76 +12,4 @@ exports.local = {
12
onError: function(error){
13
grunt.fatal(error);
14
}
19
-};
20
-
21
-if (grunt.option('debug')) {
22
- exports.local.url += '?debug=' + grunt.option('debug');
23
-}
24
-
25
-
26
-exports.saucelabs = {
27
- webdriver: {
28
- remote: {
29
- /* https://github.com/admc/wd/blob/master/README.md#named-parameters */
30
- user: process.env.SAUCE_USERNAME || 'React',
31
- pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
32
- protocol: 'http:',
33
- hostname: 'ondemand.saucelabs.com',
34
- port: '80',
35
- path: '/wd/hub'
36
- }
37
- },
38
- desiredCapabilities: {
39
- "build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
40
- "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
41
- "browserName": "chrome"
42
- },
43
- url: exports.local.url,
44
- onStart: function(browser){
45
- grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
46
- },
47
- onComplete: exports.local.onComplete,
48
- onError: exports.local.onError
49
-};
50
-
51
-/* https://saucelabs.com/docs/platforms */
52
-exports.saucelabs_ios =
53
-exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
54
-exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
55
-exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
56
-exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
57
-exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
58
-
59
-exports.saucelabs_ipad =
60
-exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
61
-exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
62
-exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
63
-exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
64
-exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
65
-
66
-exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
67
-exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
68
-
69
-exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
70
-exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
71
-exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
72
-
73
-exports.saucelabs_ie =
74
-exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
75
-exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
76
-exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
77
-exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
78
-
79
-
80
-function sauceItUp(desiredCapabilities) {
81
- desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
82
- desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
83
- return {
84
- webdriver: exports.saucelabs.webdriver,
85
- url: exports.saucelabs.url,
86
- onStart: exports.saucelabs.onStart,
87
- onComplete: exports.saucelabs.onComplete,
88
- onError: exports.saucelabs.onError,
89
- desiredCapabilities: desiredCapabilities,
90
- };
91
-}
15
+});
grunt/config/webdriver-perf.js
new
+48
@@ -0,0 +1,48 @@
1
+'use strict';
2
+
3
+var grunt = require('grunt');
4
+
5
+var tests = grunt.file.expand(__dirname + '/../../perf/tests/*');
6
+
7
+var maxTime = 5;
8
+
9
+var reactVersions = [
10
+ 'edge',
11
+ 'previous'
12
+];
13
+
14
+var params = []
15
+ .concat('headless=false')
16
+ .concat('maxTime=' + maxTime)
17
+ .concat(tests
18
+ .map(function(path){ return path.split(/tests./i).reverse()[0]; })
19
+ .map(encodeURIComponent)
20
+ .map(function(filename){ return 'test=' + filename; })
21
+ )
22
+ .concat(reactVersions
23
+ .map(encodeURIComponent)
24
+ .map(function(version){ return 'react=' + version }
25
+ )
26
+);
27
+
28
+module.exports = require('./webdriver-all')({
29
+
30
+ url: "http://127.0.0.1:9999/perf/index.html?" + params.join('&'),
31
+
32
+ isDoneTimeout: 15 * 60 * 1000,
33
+
34
+ onStart: function(){
35
+ grunt.event.on('perf results', function(results){
36
+ console.log(results);
37
+ });
38
+ },
39
+
40
+ onComplete: function(completedTestKeys){
41
+ grunt.verbose.writeln('onComplete ' + JSON.stringify(completedTestKeys));
42
+ },
43
+
44
+ onError: function(error){
45
+ grunt.fatal(error);
46
+ }
47
+
48
+});
grunt/tasks/download-previous-version.js
new
+47
@@ -0,0 +1,47 @@
1
+"use strict";
2
+
3
+var grunt = require('grunt');
4
+var http = require('http');
5
+var fs = require('fs');
6
+
7
+module.exports = function() {
8
+ var completedSuccessfully = this.async();
9
+ get(
10
+ "http://react.zpao.com/builds/master/latest/react.min.js",
11
+ __dirname + '/../../build/react-previous.min.js',
12
+ function(success){
13
+ if (!success) {
14
+ return completedSuccessfully(success);
15
+ }
16
+ get(
17
+ "http://react.zpao.com/builds/master/latest/JSXTransformer.js",
18
+ __dirname + '/../../build/JSXTransformer-previous.js',
19
+ completedSuccessfully
20
+ );
21
+ }
22
+ );
23
+
24
+ function get(url, targetFilePath, completedSuccessfully) {
25
+ grunt.verbose.writeln('getting url "' + url + '"');
26
+ http.get(url, function(response) {
27
+ grunt.verbose.writeln('Received status code ' + response.statusCode + ' for "' + url + '"');
28
+
29
+ if (response.statusCode != 200) {
30
+ if (response.headers.location) {
31
+ get(response.headers.location, targetFilePath);
32
+ return;
33
+ } else {
34
+ grunt.fatal('Nothing else to do.');
35
+ completedSuccessfully(false);
36
+ return;
37
+ }
38
+ }
39
+ grunt.verbose.writeln('Writing url to "' + targetFilePath + '"');
40
+ response.pipe(fs.createWriteStream(targetFilePath))
41
+ .on('close', function() {
42
+ completedSuccessfully(true);
43
+ })
44
+ ;
45
+ });
46
+ }
47
+};
grunt/tasks/webdriver-all.js
new
+73
@@ -0,0 +1,73 @@
1
+/* jshint evil: true */
2
+
3
+'use strict';
4
+
5
+var grunt = require("grunt");
6
+var wd = require('wd');
7
+
8
+module.exports = function task(getJSReport){
9
+ var config = this.data;
10
+ var taskSucceeded = this.async();
11
+ getJSReport = getJSReport.bind(this, config, wd);
12
+
13
+ var desiredCapabilities = {};
14
+ if (config.desiredCapabilities) {
15
+ Object.keys(config.desiredCapabilities).forEach(function(key) {
16
+ if (config.desiredCapabilities[key] === undefined) {
17
+ return;
18
+ }
19
+ desiredCapabilities[key] = config.desiredCapabilities[key];
20
+ });
21
+ }
22
+ grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
23
+
24
+ var browser = wd.promiseChainRemote(config.webdriver.remote);
25
+
26
+ browser.on('status', function(info) {
27
+ grunt.verbose.writeln(info);
28
+ });
29
+
30
+ browser.on('command', function(meth, path, data) {
31
+ grunt.verbose.writeln(' > ' + meth, path, data || '');
32
+ });
33
+
34
+ var results = null;
35
+
36
+ // browser._debugPromise();
37
+ browser
38
+ .init(desiredCapabilities)
39
+ .then(config.onStart && config.onStart.bind(config, browser))
40
+ .get(config.url)
41
+ .then(function(){return browser;})
42
+ .then(getJSReport)
43
+ .then(function(data){ results = data; })
44
+ .fail(function(error){
45
+ grunt.log.error(error);
46
+ return browser
47
+ .eval('document.documentElement.innerText || document.documentElement.textContent')
48
+ .then(grunt.verbose.writeln.bind(grunt.verbose))
49
+ .then(function(){ throw error; })
50
+ ;
51
+ })
52
+ .finally(function(){
53
+ if (grunt.option('webdriver-keep-open')) {
54
+ return;
55
+ }
56
+ grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
57
+ return browser.quit();
58
+ })
59
+ .done(
60
+ function() {
61
+ if (config.onComplete) {
62
+ config.onComplete(results);
63
+ }
64
+ taskSucceeded(true);
65
+ },
66
+ function(error) {
67
+ if (config.onError) {
68
+ config.onError(error);
69
+ }
70
+ taskSucceeded(false);
71
+ }
72
+ );
73
+};
grunt/tasks/webdriver-jasmine.js
+10
-76
@@ -2,82 +2,16 @@
2
3
'use strict';
4
5
-var grunt = require("grunt");
6
-var wd = require('wd');
7
-
5
module.exports = function(){
9
- var config = this.data;
10
- var taskSucceeded = this.async();
11
-
12
- var desiredCapabilities = {};
13
- if (config.desiredCapabilities) {
14
- Object.keys(config.desiredCapabilities).forEach(function(key) {
15
- if (config.desiredCapabilities[key] === undefined) {
16
- return;
17
- }
18
- desiredCapabilities[key] = config.desiredCapabilities[key];
19
- });
20
- }
21
- grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
22
-
23
- var browser = wd.promiseChainRemote(config.webdriver.remote);
24
-
25
- browser.on('status', function(info) {
26
- grunt.verbose.writeln(info);
6
+ return require('./webdriver-all').call(this, function(config, wd, browser){
7
+ return browser
8
+ .waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
9
+ .fail(function(error){
10
+ throw Error("The test page didn't load properly. " + error);
11
+ })
12
+ .waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
13
+ .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
14
+ .eval("jasmine.getJSReport().passed")
15
+ ;
16
});
28
-
29
- browser.on('command', function(meth, path, data) {
30
- grunt.verbose.writeln(' > ' + meth, path, data || '');
31
- });
32
-
33
- var results = null;
34
-
35
- // browser._debugPromise();
36
- browser
37
- .init(desiredCapabilities)
38
- .then(config.onStart && config.onStart.bind(config, browser))
39
- .get(config.url)
40
- .then(function(){return browser;})
41
- .then(getJSReport)
42
- .then(function(data){ results = data; })
43
- .fail(function(error){
44
- grunt.log.error(error);
45
- return browser
46
- .eval('document.documentElement.innerText || document.documentElement.textContent')
47
- .then(grunt.verbose.writeln.bind(grunt.verbose))
48
- .then(function(){ throw error; })
49
- ;
50
- })
51
- .finally(function(){
52
- if (grunt.option('webdriver-keep-open')) {
53
- return;
54
- }
55
- grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
56
- return browser.quit();
57
- })
58
- .done(
59
- function() {
60
- if (config.onComplete) {
61
- config.onComplete(results);
62
- }
63
- taskSucceeded(true);
64
- },
65
- function(error) {
66
- if (config.onError) {
67
- config.onError(error);
68
- }
69
- taskSucceeded(false);
70
- }
71
- );
17
};
73
-
74
-function getJSReport(browser){
75
- return browser
76
- .waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
77
- .fail(function(error){
78
- throw Error("The test page didn't load properly. " + error);
79
- })
80
- .waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
81
- .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
82
- .eval("jasmine.getJSReport().passed");
83
-}
grunt/tasks/webdriver-perf.js
new
+23
@@ -0,0 +1,23 @@
1
+/* jshint evil: true */
2
+
3
+'use strict';
4
+
5
+var grunt = require('grunt');
6
+
7
+module.exports = function(){
8
+ return require('./webdriver-all').call(this, function(config, wd, browser){
9
+ if (!config.isDoneTimeout) {
10
+ grunt.verbose.writeln('Expected isDoneTimeout config, using default value');
11
+ }
12
+ grunt.verbose.writeln('isDoneTimeout:' + config.isDoneTimeout);
13
+ return browser
14
+ .waitFor(wd.asserters.jsCondition("window.isDone === false"), 5e3, 50)
15
+ .fail(function(error){
16
+ throw Error("The test page didn't load properly. " + error);
17
+ })
18
+ .waitFor(wd.asserters.jsCondition("window.isDone === true"), config.isDoneTimeout || 30e3, 1e3)
19
+ .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
20
+ .eval("window.completedTestKeys || window._unhandledError")
21
+ ;
22
+ });
23
+};
package.json
+3
@@ -38,6 +38,7 @@
38
"jstransform": "~2.0.1"
39
},
40
"devDependencies": {
41
+ "benchmark": "~1.0.0",
42
"browserify": "~2.36.1",
43
"coverify": "~0.1.1",
44
"envify": "~1.0.1",
@@ -53,8 +54,10 @@
54
"grunt-contrib-jshint": "~0.7.2",
55
"gzip-js": "~0.3.2",
56
"jasmine-tapreporter": "~0.2.2",
57
+ "microtime": "~0.5.1",
58
"optimist": "~0.6.0",
59
"phantomjs": "~1.9",
60
+ "platform": "~1.0.0",
61
"populist": "~0.1.6",
62
"recast": "~0.5.6",
63
"sauce-tunnel": "~1.1.0",
perf/index.html
new
+66
@@ -0,0 +1,66 @@
1
+<!doctype html>
2
+<meta charset=utf-8>
3
+<title>Perf Tests</title>
4
+
5
+<script>
6
+window.onerror = function(error){
7
+ window._unhandledError = error;
8
+ window.isDone = true;
9
+}
10
+</script>
11
+
12
+<script src="./lib/perf-test-runner.browser.js"></script>
13
+<script> perfRunner.Polyfill(); </script>
14
+<script src="../build/react.js"></script>
15
+<script src="../build/JSXTransformer.js"></script>
16
+<script src="../test/lib/postDataToURL.browser.js"></script>
17
+<script src="./lib/BrowserPerfRunnerContext.react.js"></script>
18
+<script src="./lib/BrowserPerfRunnerApp.react.js"></script>
19
+
20
+<script>
21
+
22
+var tests = [
23
+ "sanity.js",
24
+ "todolist-mount.js",
25
+ "todolist-edit.js",
26
+ "todolist-add.js",
27
+ "todolist-do-stuff.js",
28
+ "setState-callback-5.js",
29
+ "setState-callback.js",
30
+ "basic-div.js",
31
+ "basic-unmount.js",
32
+ "renderComponent-basic.js",
33
+ "shouldComponentUpdate.js",
34
+];
35
+
36
+var reactVersions = [
37
+ 'edge',
38
+ 'builds/master/latest'
39
+];
40
+
41
+window.onload = function(){
42
+ window.isDone = false;
43
+
44
+ React.renderComponent(
45
+ BrowserPerfRunnerApp({
46
+ headless: perfRunner.getQueryParamArrayOrDefault('headless', [false])[0],
47
+ react: perfRunner.getQueryParamArrayOrDefault('react', reactVersions),
48
+ tests: perfRunner.getQueryParamArrayOrDefault('test', tests),
49
+ maxTime: perfRunner.getQueryParamArrayOrDefault('maxTime', [5])[0],
50
+ onCompleteEach: function(results){
51
+ console.log('onCompleteEach', results);
52
+ postDataToURL({type:'perf', message:results}, '/reportTestResults');
53
+ },
54
+ onError: function(error){
55
+ window._unhandledError = error;
56
+ },
57
+ onComplete: function(results){
58
+ window.completedTestKeys = Object.keys(results);
59
+ window.isDone = true;
60
+ }
61
+ }),
62
+ document.body
63
+ );
64
+}
65
+
66
+</script>
perf/lib/BrowserPerfRunnerApp.react.js
new
+205
@@ -0,0 +1,205 @@
1
+var BrowserPerfRunnerApp = React.createClass({
2
+
3
+ propTypes: {
4
+ tests: React.PropTypes.array.isRequired,
5
+ react: React.PropTypes.array.isRequired,
6
+ maxTime: React.PropTypes.number,
7
+ onCompleteEach: React.PropTypes.func,
8
+ onComplete: React.PropTypes.func,
9
+ onError: React.PropTypes.func,
10
+ headless: React.PropTypes.bool
11
+ },
12
+
13
+ getInitialState: function(){
14
+ var queue = [];
15
+ this.props.tests.forEach(function(testName){
16
+ this.props.react.forEach(function(version){
17
+ queue.push({
18
+ test: testName,
19
+ react: version
20
+ });
21
+ },this);
22
+ },this);
23
+ return {
24
+ queue: queue,
25
+ results: {}
26
+ };
27
+ },
28
+
29
+ handleResults: function(results){
30
+ this.state.results[results.test + '@' + results.react] = results;
31
+ this.replaceState(this.state);
32
+ },
33
+
34
+ handleComplete: function(queueItem){
35
+ queueItem.completed = true;
36
+
37
+ if (!this.props.onCompleteEach) {
38
+ return;
39
+ }
40
+ // Can't get the resultsForAllVersions if there are still some queued
41
+ var incompleteCount = 0;
42
+ for (var index = this.state.queue.length; --index >= 0;){
43
+ if (this.state.queue[index].completed) {
44
+ continue;
45
+ }
46
+ if (this.state.queue[index].test === queueItem.test) {
47
+ return;
48
+ }
49
+ incompleteCount ++;
50
+ }
51
+ var resultsForAllVersions = Object.keys(this.state.results)
52
+ .filter(function(key){return key.indexOf(queueItem.test) === 0;})
53
+ .map(function(key){return this.state.results[key];}, this)
54
+ ;
55
+ this.props.onCompleteEach(resultsForAllVersions);
56
+
57
+ if (this.props.onComplete && incompleteCount === 0) {
58
+ this.props.onComplete(this.state.results);
59
+ }
60
+ },
61
+
62
+ render: function(){
63
+ var grid = null;
64
+
65
+ if (!this.props.headless) {
66
+ grid = GridViewTable({
67
+ rows: this.props.tests,
68
+ cols: this.props.react,
69
+ renderCell: BrowserPerfRunnerApp.renderBenchmarkCell,
70
+ value: this.state.results
71
+ });
72
+ }
73
+
74
+ return React.DOM.div(null,
75
+ BenchmarkQueue({
76
+ initialQueue: this.state.queue,
77
+ onChange: this.handleResults,
78
+ maxTime: this.props.maxTime,
79
+ onCompleteEach: this.handleComplete,
80
+ onError: this.props.onError
81
+ }),
82
+ grid
83
+ );
84
+ }
85
+});
86
+
87
+BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col){
88
+ if (col == null && row == null) return React.DOM.th(null);
89
+ if (row == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'center'}}, col);
90
+
91
+ var benchmarks = Object.keys(props.value)
92
+ .filter(function(key){
93
+ return key.indexOf(row) === 0;
94
+ })
95
+ .map(function(key){
96
+ return props.value[key];
97
+ })
98
+ .filter(function(benchmark){
99
+ return benchmark && !benchmark.isRunning && benchmark.stats;
100
+ })
101
+ ;
102
+
103
+ if (col == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'right'}},
104
+ React.DOM.a({href:'?test=' + row}, benchmarks[0] && benchmarks[0].name || row)
105
+ );
106
+
107
+ var key = row + '@' + col;
108
+ var benchmark = props.value[key];
109
+ if (!(benchmark && benchmark.stats)) return React.DOM.td({key:key});
110
+
111
+
112
+ var colors = [
113
+ '000000',
114
+ 'AA0000',
115
+ '00AA00',
116
+ 'AA5500',
117
+ '0000AA',
118
+ 'AA00AA',
119
+ '00AAAA',
120
+ 'AAAAAA',
121
+
122
+ '555555',
123
+ 'FF5555',
124
+ '55FF55',
125
+ 'FFFF55',
126
+ '5555FF',
127
+ 'FF55FF',
128
+ '55FFFF',
129
+ 'FFFFFF'
130
+ ];
131
+
132
+ function chartValue(value){
133
+ return Math.round(valueFromRangeToRange(value, chartValue.min, chartValue.max, 0, 100));
134
+ }
135
+ chartValue.min = Math.min.apply(Math, benchmarks.map(function(benchmark){return Math.min.apply(Math, benchmark.stats.sample);}));
136
+ chartValue.max = Math.max.apply(Math, benchmarks.map(function(benchmark){return Math.max.apply(Math, benchmark.stats.sample);}));
137
+
138
+ var means = benchmarks.map(function(benchmark){
139
+ return benchmark.stats.mean;
140
+ });
141
+ benchmarks.forEach(function(benchmark){
142
+ benchmark.isTheWinner = benchmark.stats.mean <= Math.min.apply(Math, means);
143
+ });
144
+
145
+ var chartValues = benchmarks.map(function(benchmark){
146
+ // benchmark.stats.sample.sort(function(a,b){return b - a;});
147
+ return benchmark.stats.sample.map(chartValue).join(',');
148
+ }).join('|');
149
+
150
+ return (
151
+ React.DOM.td({key:key, style:{textAlign:'center', width:234, verticalAlign:'top'}},
152
+ benchmark.error && benchmark.error.message || '',
153
+ React.DOM.div({style: benchmark.isTheWinner ? { backgroundColor:'#0A5', color:'#AFA' } : {backgroundColor:'transparent', color:'inherit'}},
154
+ Math.round(1 / benchmark.stats.mean * 100) / 100, " op/s ",
155
+ React.DOM.strong(null, Math.round(benchmark.stats.mean * 1000 * 100) / 100, " ms/op "),
156
+ React.DOM.small(null, "(±" + (Math.round(benchmark.stats.rme * 10) / 10) + "%)")
157
+ ),
158
+ benchmark.isRunning && 'Running' || React.DOM.img({
159
+ style: {
160
+ borderWidth: 2,
161
+ borderStyle: 'solid',
162
+ color: '#' + colors[benchmarks.indexOf(benchmark)]
163
+ },
164
+ width: 230,
165
+ height: 50,
166
+ src: 'https://chart.googleapis.com/chart?cht=ls&chs=460x100&chd=t:' + chartValues + '&chco=' + colors.join(',')
167
+ })
168
+ )
169
+ );
170
+}
171
+
172
+function valueFromRangeToRange(value, fromMin, fromMax, toMin, toMax){
173
+ var fromRange = fromMax - fromMin;
174
+ var toRange = toMax - toMin;
175
+ return (((value - fromMin) * toRange) / fromRange) + toMin;
176
+}
177
+
178
+var GridViewTable = React.createClass({
179
+
180
+ propTypes: {
181
+ rows: React.PropTypes.array.isRequired,
182
+ cols: React.PropTypes.array.isRequired,
183
+ renderCell: React.PropTypes.func.isRequired
184
+ },
185
+
186
+ _renderCell: function(col){
187
+ return this.props.renderCell({ value:this.props.value }, this._row, col);
188
+ },
189
+
190
+ _renderRow: function(row){
191
+ this._row = row;
192
+ return React.DOM.tr({key:row},
193
+ this._renderCell(null, 0),
194
+ this.props.cols.map(this._renderCell, this)
195
+ );
196
+ },
197
+
198
+ render: function(){
199
+ return React.DOM.table(null,
200
+ this._renderRow(null, 0),
201
+ this.props.rows.map(this._renderRow, this)
202
+ );
203
+ }
204
+
205
+});
perf/lib/BrowserPerfRunnerContext.react.js
new
+195
@@ -0,0 +1,195 @@
1
+var BenchmarkQueue = React.createClass({
2
+ propTypes: {
3
+ debug: React.PropTypes.bool,
4
+ onChange: React.PropTypes.func.isRequired,
5
+ initialQueue: React.PropTypes.array.isRequired,
6
+ maxTime: React.PropTypes.number,
7
+ onCompleteEach: React.PropTypes.func,
8
+ onError: React.PropTypes.func
9
+ },
10
+
11
+ getDefaultProps: function(){
12
+ return {
13
+ maxTime: 5
14
+ };
15
+ },
16
+
17
+ getInitialState: function(){
18
+ return {
19
+ queue: this.props.initialQueue.slice()
20
+ };
21
+ },
22
+
23
+ setItemState: function(state){
24
+ state.test = this.state.queue[0].test;
25
+ state.react = this.state.queue[0].react;
26
+ this.props.onChange(state);
27
+ },
28
+
29
+ handleContextReady: function(window){
30
+ var benchmark = window.Benchmark(window.exports);
31
+ benchmark.options.maxTime = this.props.maxTime; //DEBUG
32
+
33
+ var itemState = {
34
+ testRunnerURL: window.location.href,
35
+
36
+ name: window.exports.name,
37
+ platform: window.Benchmark.platform.description,
38
+ reactVersion: window.React.version,
39
+
40
+ isMinified: (function(){
41
+ var code = window.React.renderComponent.toString();
42
+ return code.indexOf(',') - code.indexOf('(') <= 2;
43
+ }())
44
+ };
45
+
46
+ this.setItemState(itemState);
47
+
48
+ var self = this;
49
+ benchmark.on('start error cycle complete', function(){
50
+ var stats = JSON.parse(JSON.stringify(benchmark.stats));
51
+ itemState.stats = stats;
52
+ itemState.isRunning = benchmark.running;
53
+ itemState.error = benchmark.error;
54
+ self.setItemState(itemState);
55
+ });
56
+ if (this.props.onError) {
57
+ benchmark.on('error', this.props.onError);
58
+ }
59
+ benchmark.on('complete', function(){
60
+ var queue = self.state.queue.slice();
61
+ var queueItem = queue.shift();
62
+ if (self.props.onCompleteEach) {
63
+ self.props.onCompleteEach(queueItem);
64
+ }
65
+ self.setState({ queue:queue });
66
+ });
67
+ benchmark.run({async:true});
68
+ },
69
+
70
+ shouldComponentUpdate: function(nextProps, nextState){
71
+ return nextState.queue.length < this.state.queue.length;
72
+ },
73
+
74
+ render: function(){
75
+ if (!(this.state.queue && this.state.queue.length > 0)){
76
+ return React.DOM.div({style:{display:'none'}});
77
+ }
78
+ return BrowserPerfRunnerContext({
79
+ debug: this.props.debug,
80
+ test: this.state.queue[0].test,
81
+ react: this.state.queue[0].react,
82
+ onReady: this.handleContextReady
83
+ });
84
+ }
85
+});
86
+
87
+var BrowserPerfRunnerContext = React.createClass({
88
+
89
+ propTypes: {
90
+ debug: React.PropTypes.bool,
91
+ test: function(object, key){
92
+ React.PropTypes.string.isRequired(object, key);
93
+ if (/\.jsx?$/i.test(object[key])) return;
94
+ throw Error('Expected `' + key + '` to be a test file name with extension `.js` or `.jsx`');
95
+ },
96
+ react: function(object, key){
97
+ React.PropTypes.string.isRequired(object, key);
98
+ if (/^(?:builds\/.+|edge|previous|(?:\d+\.){2}\d+)$/.test(object[key])) return;
99
+ throw Error('Expected `' + key + '` prop to be a valid react version string, build string or "edge" or "previous"');
100
+ },
101
+ onReady: React.PropTypes.func.isRequired
102
+ },
103
+
104
+ getInitialState: function(){
105
+ return {
106
+ testRunnerURL:'about:blank'
107
+ };
108
+ },
109
+
110
+ // _handleFrameError: function(error){
111
+ // console.error('BrowserPerfRunnerContext', error);
112
+ // },
113
+ //
114
+ // _handleFrameLoad: function(event){
115
+ // console.log('BrowserPerfRunnerContext', event);
116
+ // },
117
+ //
118
+ _handleMessage: function(event){
119
+ if (location.href.indexOf(event.origin) !== 0)
120
+ return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.origin);
121
+ if (event.source.location.href.indexOf(this.state.testRunnerURL) === -1)
122
+ return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.source.location.href);
123
+ if (event.data !== 'Ready!')
124
+ return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message ' + JSON.stringify(event.data));
125
+
126
+ this.props.onReady(event.source);
127
+ },
128
+
129
+ _getTestRunnerURL: function(props){
130
+ return 'runner.html' +
131
+ '?' +
132
+ 'debug=' + (props.debug ? 1 : 0) +
133
+ '&' +
134
+ 'react=' + encodeURIComponent(props.react) +
135
+ '&' +
136
+ 'test=' + encodeURIComponent(props.test)
137
+ },
138
+
139
+ _renderState: function(props){
140
+ return {
141
+ testRunnerURL: this._getTestRunnerURL(props)
142
+ };
143
+ },
144
+
145
+ componentDidMount: function(){
146
+ var node = this.refs.iframe.getDOMNode();
147
+ // node.onload = this._handleFrameLoad;
148
+ // node.onerror = this._handleFrameError;
149
+ if (window.addEventListener) {
150
+ window.addEventListener('message', this._handleMessage, false);
151
+ } else if (window.attachEvent) {
152
+ window.attachEvent('onmessage', this._handleMessage);
153
+ } else {
154
+ throw Error('cannot attach onmessage listener');
155
+ }
156
+ this.setState(this._renderState(this.props));
157
+ },
158
+
159
+ componentWillUnmount: function(){
160
+ if (window.removeEventListener) {
161
+ window.removeEventListener('message', this._handleMessage);
162
+ } else if (window.detachEvent) {
163
+ window.detachEvent('onmessage', this._handleMessage);
164
+ } else {
165
+ throw Error('cannot detach onmessage listener');
166
+ }
167
+ this.refs.iframe.getDOMNode().src = '';
168
+ },
169
+
170
+ componentWillReceiveProps: function(nextProps){
171
+ this.setState(this._renderState(nextProps));
172
+ },
173
+
174
+ shouldComponentUpdate: function(nextProps, nextState){
175
+ return nextState.testRunnerURL != this.state.testRunnerURL;
176
+ },
177
+
178
+ render: function(){
179
+ return (
180
+ React.DOM.iframe({
181
+ ref: 'iframe',
182
+ name: "BrowserPerfRunnerContextFrame",
183
+ style: this.style,
184
+ src: this.state.testRunnerURL
185
+ })
186
+ );
187
+ },
188
+
189
+ style: {
190
+ position: 'absolute',
191
+ right: '100%',
192
+ bottom: '100%'
193
+ }
194
+
195
+});
perf/lib/perf-test-runner.browser.js
new
+204
@@ -0,0 +1,204 @@
1
+if (typeof console == 'undefined') console = {
2
+ log: function(){},
3
+ warn: function(){},
4
+ error: function(){},
5
+ debug: function(){}
6
+};
7
+
8
+var perfRunner;
9
+if (typeof exports == 'object') {
10
+ perfRunner = exports;
11
+} else {
12
+ perfRunner = {};
13
+}
14
+
15
+perfRunner.assert = function(test, message){
16
+ if (typeof test == 'function') test = test();
17
+ if (test) return;
18
+ throw Error(message);
19
+}
20
+
21
+perfRunner.WriteScript = function(props){
22
+ var type = '';
23
+ if (props.jsx) {
24
+ type = ' type="text/jsx"';
25
+ }
26
+ var src = props.src;
27
+ if (!props.cache) {
28
+ src += src.indexOf('?') === -1 ? '?_' : '&_';
29
+ src += perfRunner.WriteScript.cacheBust;
30
+ }
31
+ document.write('<script' + type + ' src="' + src + '"><\/script>');
32
+}
33
+perfRunner.WriteScript.cacheBust = (+new Date).toString(36);
34
+
35
+perfRunner.WriteReactLibScript = function(params){
36
+ var src;
37
+ var minSuffix;
38
+ if (params.debug) {
39
+ minSuffix = '';
40
+ } else {
41
+ minSuffix = '.min';
42
+ }
43
+
44
+ if (params.version && typeof params.version != 'string') throw TypeError("Expected 'version' to be a string");
45
+
46
+ if (params.version == 'edge' || !params.version) {
47
+ console.log('React edge (local)');
48
+ perfRunner.WriteScript({src:'../build/react' + minSuffix + '.js'});
49
+ perfRunner.WriteScript({src:'../build/JSXTransformer.js'});
50
+ } else if (params.version == 'previous') {
51
+ console.log('React previous (local)');
52
+ perfRunner.WriteScript({cache:true, src:'../build/react-previous' + minSuffix + '.js'});
53
+ perfRunner.WriteScript({cache:true, src:'../build/JSXTransformer-previous.js'});
54
+ } else if (params.version.indexOf('builds/') === 0) {
55
+ perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/react' + minSuffix + '.js'});
56
+ perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/JSXTransformer.js'});
57
+ } else {
58
+ console.log('React ' + params.version);
59
+ perfRunner.WriteScript({cache:true, src:'http://fb.me/react-' + params.version + minSuffix + '.js'});
60
+ perfRunner.WriteScript({cache:true, src:'http://fb.me/JSXTransformer-' + params.version + '.js'});
61
+ }
62
+ if (params.debug) {
63
+ console.warn('Loading the unminified build of React, performance may suffer.');
64
+ console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '" for better perf.');
65
+ } else {
66
+ console.warn('Loading the minified build of React, debugging may be harder.');
67
+ console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '&debug=1' + '" for easier debugging.');
68
+ }
69
+}
70
+
71
+perfRunner.WriteTestScript = function(params){
72
+ if (Array.isArray(params.test)) {
73
+ return params.test
74
+ .map(function(test){return {test:test};})
75
+ .map(perfRunner.WriteTestScript)
76
+ ;
77
+ }
78
+ perfRunner.assert(params.test.indexOf('..') === -1, 'no relative paths allowed');
79
+ return perfRunner.WriteScript({jsx:true, src: './tests/' + params.test});
80
+}
81
+
82
+perfRunner.getQueryParamArray = function(key){
83
+ var values;
84
+ var queryString = location.search.substr(1);
85
+ var _key = encodeURIComponent(key) + '=';
86
+
87
+ if (queryString.indexOf(_key) > -1) {
88
+ values = queryString
89
+ .split(_key)
90
+ .slice(1)
91
+ .map(function(part){return part.split('&')[0];})
92
+ .map(decodeURIComponent)
93
+ .map(function(string){
94
+ try {
95
+ return JSON.parse(string);
96
+ } catch(e){}
97
+ return string;
98
+ })
99
+ ;
100
+ }
101
+
102
+ perfRunner.assert(values && values.length && values[0], 'expected ' + key + ' query param');
103
+ return values;
104
+}
105
+
106
+perfRunner.getQueryParamArrayOrDefault = function(key, defaultValue){
107
+ try {
108
+ return perfRunner.getQueryParamArray(key);
109
+ } catch (e) {}
110
+ return defaultValue;
111
+}
112
+
113
+perfRunner.Polyfill = function(){
114
+ if (typeof Function.prototype.bind != 'undefined') return;
115
+ perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-shim.js', cache:true});
116
+ perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-sham.js', cache:true});
117
+}
118
+
119
+perfRunner.BenchmarkResults = function(props){
120
+ return perfRunner.roundNumberWithPrecision(props.stats.mean * 1000) + 'ms/op'
121
+}
122
+
123
+perfRunner.roundNumberWithPrecision = function(number, precision){
124
+ if (!precision) precision = 1000;
125
+ return Math.round(number * precision) / precision;
126
+}
127
+
128
+perfRunner.quickBench = function(benchmarkOptions, onComplete, onBeforeStart){
129
+ var bench = new Benchmark(benchmarkOptions);
130
+ if (onBeforeStart) onBeforeStart(null, bench);
131
+
132
+ bench.on('error', function(event){
133
+ console.error(event.message);
134
+ console.log(event.target.compiled.toString());
135
+ onComplete(Error(event.error));
136
+ });
137
+
138
+ bench.on('start', function(){
139
+ console.log('starting', bench.name);
140
+ });
141
+
142
+ bench.on('cycle', function(){
143
+ var bench = this,
144
+ size = bench.stats.size;
145
+
146
+ if (!bench.aborted) {
147
+ console.warn(bench.name + ' × ' + bench.count +
148
+ ' (' + bench.stats.sample.length + ' samples)' +
149
+ ' (' + Math.round(1 / bench.stats.mean) + ' ops/sec' + ')' +
150
+ ' (' + (bench.stats.mean * 1000) + ' ms/op' + ')' +
151
+ ' (±' + bench.stats.rme.toFixed(2) + '%)' +
152
+ ' with ' + React.version
153
+ );
154
+ }
155
+ });
156
+
157
+ bench.on('complete', function(){
158
+ var results = {
159
+ platform: Benchmark.platform.description,
160
+ react: React.version,
161
+ name: bench.name,
162
+ // times: bench.times,
163
+ // stats: bench.stats
164
+ };
165
+
166
+ results['s/op'] = bench.stats.mean
167
+ results['ms/op'] = results['s/op'] * 1000
168
+ results['op/s'] = 1 / results['s/op']
169
+ results["% frame 60"] = results['ms/op'] / (1000 / 60) * 100
170
+
171
+ console.log(results);
172
+ onComplete(null, results);
173
+ });
174
+
175
+ bench.run();
176
+};
177
+
178
+perfRunner.singleTest = function(benchmarkOptions, onComplete){
179
+ var bench = Benchmark(exports);
180
+ bench.on('complete', function(){
181
+ var results = {
182
+ platform: Benchmark.platform.description,
183
+ react: React.version,
184
+ name: bench.name,
185
+ stats: bench.stats
186
+ };
187
+ onComplete(results);
188
+ });
189
+ bench.run();
190
+}
191
+
192
+perfRunner.ViewObject = function(props){
193
+ var value = props.value;
194
+ delete props.value;
195
+
196
+ if (typeof value != 'object') return React.DOM.span(props, [JSON.stringify(value), " ", typeof value]);
197
+
198
+ return React.DOM.table(props, Object.keys(value).map(function(key){
199
+ return React.DOM.tr(null,
200
+ React.DOM.th(null, key),
201
+ React.DOM.td(null, perfRunner.ViewObject({key:key, value:value[key]}))
202
+ );
203
+ }));
204
+}
perf/lib/todolist.browser.js
new
+117
@@ -0,0 +1,117 @@
1
+/*global*/todolist = {};
2
+
3
+todolist.ID = Date.now();
4
+
5
+todolist.now = window.performance && window.performance.now && window.performance.now.bind(window.performance) || Date.now.bind(Date);
6
+
7
+todolist.App = React.createClass({
8
+ propTypes: {
9
+ fakeDataCount: React.PropTypes.number
10
+ },
11
+
12
+ getInitialState: function(){
13
+ var todos;
14
+ if (this.props.fakeDataCount) {
15
+ todos = Array(this.props.fakeDataCount + 1).join(',').split(',').map(function(ignore, index){
16
+ return {id:index, title:"Title " + index + " " + Math.random().toString(36).substring(2,16), completed:!!(index % 2)};
17
+ });
18
+ }
19
+ return {
20
+ timerStart: todolist.now(),
21
+ timerEnd: null,
22
+ timerEvent: 'getInitialState',
23
+ todos: todos || this.props.initialData || []
24
+ };
25
+ },
26
+ componentWillUpdate: function(props, state){
27
+ state.todos = state.todos.filter(function(todo){
28
+ return !todo.deleted;
29
+ });
30
+ },
31
+ addItem: function(title, callback){
32
+ if (title == null || title === '') {
33
+ var error = Error('invalid title');
34
+ if (!callback) throw error;
35
+ return callback(error);
36
+ }
37
+ var todos = this.state.todos.slice();
38
+ var todo = {
39
+ id: ++todolist.ID,
40
+ title: title,
41
+ completed: false
42
+ };
43
+ todos.push(todo);
44
+ if (callback) callback = callback.bind(this, todo);
45
+ this.setState({ timerEvent:'addItem', timerStart:todolist.now(), timerEnd:null, todos:todos }, callback);
46
+ return todo;
47
+ },
48
+ deleteItemById: function(id, callback){
49
+ var todo = this._getById(id);
50
+ if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
51
+ todo.deleted = true;
52
+ this.setState({ timerEvent:'deleteItemById', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
53
+ },
54
+ setItemCompleted: function(id, completed, callback){
55
+ var todo = this._getById(id);
56
+ if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
57
+ todo.completed = completed;
58
+ this.setState({ timerEvent:'setItemCompleted', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
59
+ },
60
+ _getById: function(id){
61
+ id = +id;
62
+ var todos = this.state.todos;
63
+ for (var index = todos.length; --index >= 0;){
64
+ if (todos[index].id === id) return todos[index];
65
+ }
66
+ return null;
67
+ },
68
+ _handleItemCompletedCheckboxChange: function(event){
69
+ var node = event.target;
70
+ this.setItemCompleted(node.value, node.checked);
71
+ },
72
+ _handleItemDeletedButton: function(event){
73
+ var node = event.target;
74
+ this.deleteItemById(node.value);
75
+ },
76
+ _renderTodoItem: function(todo, index){
77
+ return (
78
+ React.DOM.li({key:todo.id},
79
+ React.DOM.button({value:todo.id, onClick:this._handleItemDeletedButton}, 'x'),
80
+ React.DOM.input({type:"checkbox", value:todo.id, checked:todo.completed, onChange:this._handleItemCompletedCheckboxChange}),
81
+ " ",
82
+ React.DOM.span({style:{"text-decoration":todo.completed ? "line-through" : ""}}, todo.title)
83
+ )
84
+ );
85
+ },
86
+ render: function(){
87
+ if (!this.state.timerEnd) this.state.timerEnd = todolist.now();
88
+ if (this.props.onRender) this.props.onRender();
89
+ return (
90
+ React.DOM.div(null,
91
+ React.DOM.h1(null, "TODO"),
92
+ React.DOM.h3(null, this.state.timerEvent, " ", this.state.timerEnd - this.state.timerStart, 'ms'),
93
+ todolist.NewItemForm({onEnter:this.addItem, autoFocus:true}),
94
+ React.DOM.ol(null, this.state.todos.map(this._renderTodoItem))
95
+ )
96
+ );
97
+ },
98
+ componentDidMount: function(rootNode){
99
+ if (this.props.onDidMount) this.props.onDidMount(rootNode);
100
+ }
101
+});
102
+
103
+todolist.NewItemForm = React.createClass({
104
+ _handleNewItemKeyDown: function(event){
105
+ if (event.which !== 13/*enter key*/) return;
106
+ var node = this.refs.text.getDOMNode();
107
+ var value = node.value;
108
+ node.value = '';
109
+ this.props.onEnter(value);
110
+ return false;
111
+ },
112
+ render: function(){
113
+ return this.transferPropsTo(
114
+ React.DOM.input({ref:"text", onKeyDown:this.props.onEnter && this._handleNewItemKeyDown})
115
+ );
116
+ }
117
+});
perf/lib/todolist.html
new
+16
@@ -0,0 +1,16 @@
1
+<!doctype html>
2
+<meta charset=utf-8>
3
+<title>todolist</title>
4
+
5
+<script src="perf-test-runner.browser.js"></script>
6
+<script> perfRunner.Polyfill(); </script>
7
+<script src="../../build/react.min.js"></script>
8
+<script src="todolist.browser.js"></script>
9
+
10
+<script>
11
+function main(){
12
+ var app = todolist.App({ fakeDataCount:333 });
13
+ React.renderComponent(app, document.body);
14
+}
15
+window.onload = main;
16
+</script>
perf/runner.html
new
+59
@@ -0,0 +1,59 @@
1
+<!doctype html>
2
+<meta charset=utf-8>
3
+<title>Perf Test Runner</title>
4
+
5
+<script src="../node_modules/platform/platform.js"></script>
6
+<script src="../node_modules/benchmark/benchmark.js"></script>
7
+<script src="lib/perf-test-runner.browser.js"></script>
8
+
9
+<script> perfRunner.Polyfill(); </script>
10
+
11
+<script>
12
+
13
+perfRunner.WriteReactLibScript({
14
+ version: perfRunner.getQueryParamArrayOrDefault('react', [null])[0],
15
+ debug: perfRunner.getQueryParamArrayOrDefault('debug', [false])[0]
16
+});
17
+
18
+perfRunner.WriteScript({src:"lib/todolist.browser.js"});
19
+
20
+perfRunner.WriteTestScript({ test:perfRunner.getQueryParamArray('test') });
21
+
22
+</script>
23
+
24
+<script>
25
+
26
+function checkIfReady(){
27
+ if (!window.exports) return setTimeout(checkIfReady, 50);
28
+
29
+ console.timeStamp && console.timeStamp(perfRunner.getQueryParamArray('test'));
30
+ if (typeof gc == 'function') gc();
31
+ window.parent.postMessage(perfRunner.getQueryParamArrayOrDefault('callback', ['Ready!'])[0], '*');
32
+}
33
+checkIfReady();
34
+
35
+function main(callback){
36
+ perfRunner.singleTest(exports, function(results){
37
+ document.getElementById('results').textContent = JSON.stringify(results, null, 2);
38
+ callback();
39
+ });
40
+}
41
+
42
+function runOnce(callback){
43
+ setTimeout(function(){
44
+ exports.fn({resolve: function(){
45
+ setTimeout(callback, 0);
46
+ }});
47
+ },0);
48
+}
49
+
50
+</script>
51
+<button autofocus onclick="if (disabled) return false; main(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run!</button>
52
+<div>
53
+ <button onclick="exports.setup();">Setup!</button>
54
+ <button onclick="if (disabled) return false; runOnce(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run once!</button>
55
+ <button onclick="exports.teardown();">Teardown!</button>
56
+</div>
57
+
58
+<pre id=results style="font-size:9px"></pre>
59
+
perf/tests/basic-div.js
new
+16
@@ -0,0 +1,16 @@
1
+/* jshint undef: true, unused: true */
2
+
3
+/* global document */
4
+/* global window */
5
+/* global Benchmark */
6
+/* global React */
7
+
8
+if (typeof exports == 'undefined') exports = {};
9
+
10
+/*http://benchmarkjs.com/docs#options*/
11
+
12
+exports.name = 'React.DOM.div, no props';
13
+
14
+exports.fn = function(){
15
+ React.DOM.div(null, 'lol, perf testing ', this.count);
16
+};
perf/tests/basic-unmount.js
new
+23
@@ -0,0 +1,23 @@
1
+/* jshint undef: true, unused: true */
2
+
3
+/* global document */
4
+/* global window */
5
+/* global Benchmark */
6
+/* global React */
7
+
8
+if (typeof exports == 'undefined') exports = {};
9
+
10
+/*http://benchmarkjs.com/docs#options*/
11
+
12
+exports.name = 'unmountComponentAtNode';
13
+
14
+exports.setup = function(){
15
+ /*global*/_rootNode = document.createElement('div');
16
+ document.body.appendChild(_rootNode);
17
+ var _firstChild = React.DOM.div(null, 'lol, perf testing ', this.count);
18
+ React.renderComponent(_firstChild, _rootNode);
19
+};
20
+exports.fn = function(){
21
+ if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
22
+ else React.unmountComponentAtNode(_rootNode);
23
+};
perf/tests/renderComponent-basic.js
new
+24
@@ -0,0 +1,24 @@
1
+/* jshint undef: true, unused: true */
2
+
3
+/* global document */
4
+/* global window */
5
+/* global Benchmark */
6
+/* global React */
7
+
8
+if (typeof exports == 'undefined') exports = {};
9
+
10
+/*http://benchmarkjs.com/docs#options*/
11
+
12
+exports.name = 'React.renderComponent single div';
13
+
14
+exports.setup = function(){
15
+ /*global*/_rootNode = document.createElement('div');
16
+ document.body.appendChild(_rootNode);
17
+};
18
+exports.fn = function(){
19
+ React.renderComponent(React.DOM.div(null, 'lol, perf testing ', this.count), _rootNode);
20
+};
21
+exports.teardown = function(){
22
+ if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
23
+ else React.unmountComponentAtNode(_rootNode);
24
+};
perf/tests/sanity.js
new
+15
@@ -0,0 +1,15 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'Trivial benchmark to verify that everything works';
6
+
7
+exports.setup = function(){
8
+ var foo;
9
+};
10
+exports.fn = function(){
11
+ foo = Array(999).join('Howdy!\n');
12
+};
13
+exports.teardown = function(){
14
+ foo = null;
15
+};
perf/tests/setState-callback-5.js
new
+41
@@ -0,0 +1,41 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'From setState to callback (x5)';
6
+
7
+exports.defer = true;
8
+
9
+exports.setup = function(){
10
+ /*global*/_rootNode = document.createElement('div');
11
+ document.body.appendChild(_rootNode);
12
+ /*global*/setState = null;
13
+
14
+ var AwesomeComponent = React.createClass({
15
+ getInitialState: function(){
16
+ return { random:null };
17
+ },
18
+ render: function(){
19
+ if (!setState) setState = this.setState.bind(this);
20
+ return React.DOM.div(null, this.state.random);
21
+ }
22
+ });
23
+
24
+ React.renderComponent(AwesomeComponent(null), _rootNode);
25
+};
26
+exports.fn = function(deferred){
27
+ setState({random: Date.now() + Math.random()}, function(){
28
+ setState({random: Date.now() + Math.random()}, function(){
29
+ setState({random: Date.now() + Math.random()}, function(){
30
+ setState({random: Date.now() + Math.random()}, function(){
31
+ setState({random: Date.now() + Math.random()}, function(){
32
+ deferred.resolve();
33
+ });
34
+ });
35
+ });
36
+ });
37
+ });
38
+};
39
+exports.teardown = function(){
40
+ React.unmountComponentAtNode(_rootNode);
41
+};
perf/tests/setState-callback.js
new
+33
@@ -0,0 +1,33 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'From setState to callback';
6
+
7
+exports.defer = true;
8
+
9
+exports.setup = function(){
10
+ /*global*/_rootNode = document.createElement('div');
11
+ document.body.appendChild(_rootNode);
12
+ /*global*/setState = null;
13
+
14
+ var AwesomeComponent = React.createClass({
15
+ getInitialState: function(){
16
+ return { random:null };
17
+ },
18
+ render: function(){
19
+ if (!setState) setState = this.setState.bind(this);
20
+ return React.DOM.div(null, this.state.random);
21
+ }
22
+ });
23
+
24
+ React.renderComponent(AwesomeComponent(null), _rootNode);
25
+};
26
+exports.fn = function(deferred){
27
+ setState({random: Date.now() + Math.random()}, function(){
28
+ deferred.resolve();
29
+ });
30
+};
31
+exports.teardown = function(){
32
+ React.unmountComponentAtNode(_rootNode);
33
+};
perf/tests/shouldComponentUpdate.js
new
+25
@@ -0,0 +1,25 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'shouldComponentUpdate';
6
+
7
+exports.setup = function(){
8
+ var AwesomeComponent = React.createClass({
9
+ shouldComponentUpdate: function(){
10
+ return false;
11
+ },
12
+ render: function(){
13
+ return React.DOM.div({});
14
+ }
15
+ });
16
+
17
+ var _rootNode = document.createElement('div');
18
+ document.body.appendChild(_rootNode);
19
+};
20
+exports.fn = function(){
21
+ React.renderComponent(AwesomeComponent(null), _rootNode);
22
+};
23
+exports.teardown = function(){
24
+ React.unmountComponentAtNode(_rootNode);
25
+};
perf/tests/todolist-add.js
new
+27
@@ -0,0 +1,27 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'todolist from addItem to callback';
6
+
7
+exports.defer = true;
8
+
9
+exports.setup = function(){
10
+ /*global*/_rootNode = document.createElement('div');
11
+ document.body.appendChild(_rootNode);
12
+ /*global*/_app = todolist.App({ fakeDataCount: 333 });
13
+ React.renderComponent(_app, _rootNode);
14
+};
15
+exports.fn = function(deferred){
16
+ var liCount = document.getElementsByTagName('li').length;
17
+ _app.addItem(Math.random(), function(){
18
+ if (document.getElementsByTagName('li').length <= liCount) throw Error('expected a list item to be added to the dom');
19
+ deferred.resolve();
20
+ });
21
+};
22
+exports.teardown = function(){
23
+ React.unmountComponentAtNode(_rootNode);
24
+ _rootNode.parentNode.removeChild(_rootNode);
25
+ _rootNode = null;
26
+ _app = null;
27
+};
perf/tests/todolist-do-stuff.js
new
+54
@@ -0,0 +1,54 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+/*global*/_timesToRun = 2;
6
+
7
+exports.name = 'todolist add, complete, remove (x' + _timesToRun + ')';
8
+
9
+exports.defer = true;
10
+
11
+exports.setup = function(){
12
+ /*global*/_rootNode = document.createElement('div');
13
+ document.body.appendChild(_rootNode);
14
+ /*global*/_app = todolist.App({ fakeDataCount: 333 });
15
+ React.renderComponent(_app, _rootNode);
16
+};
17
+
18
+exports.fn = function(deferred){
19
+ var originalLiCount = document.getElementsByTagName('li').length;
20
+
21
+ var todos = [];
22
+ var times = _timesToRun;
23
+ while (times-- >= 0){
24
+ todos.push(_app.addItem(times+1));
25
+ }
26
+
27
+ todos.forEach(function(todo){
28
+ _app.setItemCompleted(todo.id);
29
+ });
30
+
31
+ todos.forEach(function(todo){
32
+ _app.deleteItemById(todo.id);
33
+ });
34
+
35
+ todos = null;
36
+
37
+ _app.addItem(Math.random(), function(todo){
38
+ if (document.getElementsByTagName('li').length <= originalLiCount)
39
+ throw Error('expected a list item to be added to the dom');
40
+
41
+ _app.deleteItemById(todo.id, function(){
42
+ if (document.getElementsByTagName('li').length != originalLiCount)
43
+ throw Error('expected everything to be done by now');
44
+ deferred.resolve();
45
+ });
46
+ });
47
+};
48
+
49
+exports.teardown = function(){
50
+ React.unmountComponentAtNode(_rootNode);
51
+ _rootNode.parentNode.removeChild(_rootNode);
52
+ _rootNode = null;
53
+ _app = null;
54
+};
perf/tests/todolist-edit.js
new
+32
@@ -0,0 +1,32 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'todolist setItemCompleted';
6
+
7
+exports.defer = true;
8
+
9
+exports.setup = function(){
10
+ /*global*/_rootNode = document.createElement('div');
11
+ document.body.appendChild(_rootNode);
12
+ /*global*/_app = todolist.App({ fakeDataCount: 333 });
13
+ React.renderComponent(_app, _rootNode);
14
+ /*global*/_todo1 = _app.addItem("Howdy 1!");
15
+ /*global*/_todo2 = _app.addItem("Howdy 2!");
16
+ /*global*/_todo3 = _app.addItem("Howdy 3!");
17
+};
18
+
19
+exports.fn = function(deferred){
20
+ _app.setItemCompleted(_todo1.id, !_todo1.completed);
21
+ _app.setItemCompleted(_todo2.id, !_todo2.completed);
22
+ _app.setItemCompleted(_todo3.id, !_todo3.completed, function(){
23
+ deferred.resolve();
24
+ });
25
+};
26
+
27
+exports.teardown = function(){
28
+ React.unmountComponentAtNode(_rootNode);
29
+ _rootNode.parentNode.removeChild(_rootNode);
30
+ _rootNode = null;
31
+ _app = null;
32
+};
perf/tests/todolist-mount.js
new
+23
@@ -0,0 +1,23 @@
1
+if (typeof exports == 'undefined') exports = {};
2
+
3
+/*http://benchmarkjs.com/docs#options*/
4
+
5
+exports.name = 'todolist from renderComponent to renderComponent callback (333 rows)';
6
+
7
+exports.defer = true;
8
+
9
+exports.setup = function(){
10
+ if (typeof _rootNode != 'undefined') throw Error("should teardown before running setup again");
11
+ /*global*/_rootNode = document.createElement('div');
12
+ document.body.appendChild(_rootNode);
13
+};
14
+
15
+exports.fn = function(deferred){
16
+ React.renderComponent(todolist.App({ fakeDataCount: 333 }), _rootNode, function(){ deferred.resolve(); });
17
+};
18
+
19
+exports.teardown = function(){
20
+ React.unmountComponentAtNode(_rootNode);
21
+ _rootNode.parentNode.removeChild(_rootNode);
22
+ _rootNode = undefined;
23
+};
test/index.html
+1
@@ -11,6 +11,7 @@
11
'../node_modules/jasmine-tapreporter/src/tapreporter.js',
12
'../vendor/jasmine-jsreporter/jasmine-jsreporter.js',
13
14
+ 'lib/postDataToURL.browser.js',
15
'lib/reportTestResults.browser.js',
16
17
'../build/react.js',
test/lib/postDataToURL.browser.js
new
+34
@@ -0,0 +1,34 @@
1
+function createXMLHttpRequest(){
2
+ try{return new XMLHttpRequest();}
3
+ catch(e){}
4
+ try {return new ActiveXObject("Msxml2.XMLHTTP");}
5
+ catch (e) {}
6
+ try {return new ActiveXObject("Microsoft.XMLHTTP");}
7
+ catch (e) {}
8
+}
9
+
10
+function getURLSync(url){
11
+ var request = createXMLHttpRequest();
12
+ request.open('GET', url, /*asynchronous?*/false);
13
+ return request.responseText;
14
+}
15
+
16
+function postDataToURL(data, url, callback) {
17
+ if (!callback) callback = postDataToURL.defaultCallback;
18
+ var request = createXMLHttpRequest();
19
+ if (!request) return callback(Error('XMLHttpRequest is unsupported'));
20
+ postDataToURL.running = (postDataToURL.running||0) + 1;
21
+ request.onreadystatechange = function(){
22
+ if (request.readyState != 4) return;
23
+ request.onreadystatechange = null;
24
+ postDataToURL.running = (postDataToURL.running||0) - 1;
25
+ callback(request.status == 200 ? null : request.status, request.responseText);
26
+ };
27
+ request.open('POST', url);
28
+ request.setRequestHeader('Content-Type', 'application/json');
29
+ request.send(JSON.stringify(data));
30
+}
31
+
32
+postDataToURL.defaultCallback = function(error){
33
+ // console.log('postDataToURL.defaultCallback', arguments)
34
+}
test/lib/reportTestResults.browser.js
-27
@@ -48,30 +48,3 @@ console._flush = function(){
48
};
49
50
}(window.jasmine.getEnv()));
51
-
52
-function createXMLHttpRequest(){
53
- try{return new XMLHttpRequest();}
54
- catch(e){}
55
- try {return new ActiveXObject("Msxml2.XMLHTTP");}
56
- catch (e) {}
57
- try {return new ActiveXObject("Microsoft.XMLHTTP");}
58
- catch (e) {}
59
-}
60
-function postDataToURL(data, url, callback) {
61
- if (!callback) callback = postDataToURL.defaultCallback;
62
- var request = createXMLHttpRequest();
63
- if (!request) return callback(Error('XMLHttpRequest is unsupported'));
64
- postDataToURL.running = (postDataToURL.running||0) + 1;
65
- request.onreadystatechange = function(){
66
- if (request.readyState != 4) return;
67
- request.onreadystatechange = null;
68
- postDataToURL.running = (postDataToURL.running||0) - 1;
69
- callback(request.status == 200 ? null : request.status, request.responseText);
70
- };
71
- request.open('POST', url);
72
- request.setRequestHeader('Content-Type', 'application/json');
73
- request.send(JSON.stringify(data));
74
-}
75
-postDataToURL.defaultCallback = function(error){
76
- // console.log('postDataToURL.defaultCallback', arguments)
77
-}