From 7f0b7e04dd00916c8a5c5a5fb9cf557f9c010055 Mon Sep 17 00:00:00 2001 From: Isiah Meadows Date: Thu, 31 Aug 2017 05:31:07 -0400 Subject: [PATCH 1/5] Make the benchmark a little more precise We special-case identical vnode trees, so it's better that we test both identical and rebuilt trees when benchmarking rendering. --- performance/test-perf.js | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/performance/test-perf.js b/performance/test-perf.js index 2ba9b622..89160a05 100644 --- a/performance/test-perf.js +++ b/performance/test-perf.js @@ -70,7 +70,7 @@ suite.on("complete", function() { suite.on("error", console.error.bind(console)) suite.add({ - name : "rerender without changes", + name : "rerender identical vnode", onStart : function() { this.vdom = m("div", {class: "foo bar", "data-foo": "bar", p: 2}, m("header", @@ -119,6 +119,53 @@ suite.add({ } }) +suite.add({ + name : "rerender same tree", + fn : function() { + m.render(scratch, m("div", {class: "foo bar", "data-foo": "bar", p: 2}, + m("header", + m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"), + m("nav", + m("a", {href: "/foo"}, "Foo"), + m("a", {href: "/bar"}, "Bar") + ) + ), + m("main", + m("form", {onSubmit: function onSubmit() {}}, + m("input", {type: "checkbox", checked: true}), + m("input", {type: "checkbox", checked: false}), + m("fieldset", + m("label", + m("input", {type: "radio", checked: true}) + ), + m("label", + m("input", {type: "radio"}) + ) + ), + m("button-bar", + m("button", + {style: "width:10px; height:10px; border:1px solid #FFF;"}, + "Normal CSS" + ), + m("button", + {style: "top:0 ; right: 20"}, + "Poor CSS" + ), + m("button", + {style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true}, + "Poorer CSS" + ), + m("button", + {style: {margin: 0, padding: "10px", overflow: "visible"}}, + "Object CSS" + ) + ) + ) + ) + )) + } +}) + suite.add({ name : "construct large VDOM tree", From b345e4f023f236b4ff65d946e1b530e81511bf0d Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Fri, 4 Aug 2017 09:23:20 +0200 Subject: [PATCH 2/5] Support promise return in ospec --- ospec/README.md | 33 +++++++++++++++++++++++- ospec/ospec.js | 54 +++++++++++++++++++++++++-------------- ospec/tests/test-ospec.js | 45 +++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 21 deletions(-) diff --git a/ospec/README.md b/ospec/README.md index 68a39dbe..dc3d923e 100644 --- a/ospec/README.md +++ b/ospec/README.md @@ -148,6 +148,22 @@ o("setTimeout calls callback", function(done) { }) ``` +Alternativly you can return a promise or even use an async function in tests: + +```javascript +o("promise test", function() { + return new Promise(function(resolve) { + setTimeout(resolve, 10) + }) +}) +``` + +```javascript +o("promise test", async function() { + await someOtherAsyncFunction() +}) +``` + By default, asynchronous tests time out after 20ms. This can be changed on a per-test basis using the `timeout` argument: ```javascript @@ -158,7 +174,22 @@ o("setTimeout calls callback", function(done, timeout) { }) ``` -Note that the `timeout` function call must be the first statement in its test. +Note that the `timeout` function call must be the first statement in its test. This currently does not work for promise tests. You can combine both methods to do this: + +```javascript +o("promise test", function(done, timeout) { + timeout(1000) + someOtherAsyncFunctionThatTakes900ms().then(done) +}) +``` + +```javascript +o("promise test", async function(done, timeout) { + timeout(1000) + await someOtherAsyncFunctionThatTakes900ms() + done() +}) +``` Asynchronous tests generate an assertion that succeeds upon calling `done` or fails on timeout with the error message `async test timed out`. diff --git a/ospec/ospec.js b/ospec/ospec.js index 26d5c195..cac962a6 100644 --- a/ospec/ospec.js +++ b/ospec/ospec.js @@ -84,23 +84,34 @@ module.exports = new function init(name) { if (cursor === fns.length) return var fn = fns[cursor++] + var timeout = 0, delay = 200, s = new Date + var isDone = false + + function done() { + if (timeout !== undefined) { + timeout = clearTimeout(timeout) + if (delay !== Infinity) record(null) + if (!isDone) next() + else throw new Error("`" + arg + "()` should only be called once") + isDone = true + } + else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms") + } + + function startTimer() { + timeout = setTimeout(function() { + timeout = undefined + record("async test timed out") + next() + }, Math.min(delay, 2147483647)) + } + if (fn.length > 0) { - var timeout = 0, delay = 200, s = new Date - var isDone = false var body = fn.toString() var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop() if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once") try { - fn(function done() { - if (timeout !== undefined) { - timeout = clearTimeout(timeout) - if (delay !== Infinity) record(null) - if (!isDone) next() - else throw new Error("`" + arg + "()` should only be called once") - isDone = true - } - else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms") - }, function(t) {delay = t}) + fn(done, function(t) {delay = t}) } catch (e) { record(e.message, e) @@ -108,16 +119,21 @@ module.exports = new function init(name) { next() } if (timeout === 0) { - timeout = setTimeout(function() { - timeout = undefined - record("async test timed out") - next() - }, Math.min(delay, 2147483647)) + startTimer() } } else { - fn() - nextTickish(next) + var p = fn() + if (p && p.then) { + startTimer() + p.then(done, e => { + record(e.message, e) + subjects.pop() + next() + }) + } else { + nextTickish(next) + } } } } diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index 0367e6ec..dfc3f7b0 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -107,7 +107,7 @@ o.spec("ospec", function() { o(output).deepEquals({tag: "div", children: children}) }) }) - o.spec("async", function() { + o.spec("async callback", function() { var a = 0, b = 0 o.before(function(done) { @@ -148,4 +148,47 @@ o.spec("ospec", function() { }) }) }) + + o.spec("async promise", function() { + var a = 0, b = 0 + + function wrapPromise(fn) { + return new Promise(resolve => { + callAsync(() => { + fn() + resolve() + }) + }) + } + + o.before(function() { + return wrapPromise(() => { + a = 1 + }) + }) + + o.after(function() { + return wrapPromise(function() { + a = 0 + }) + }) + + o.beforeEach(function() { + return wrapPromise(function() { + b = 1 + }) + }) + o.afterEach(function() { + return wrapPromise(function() { + b = 0 + }) + }) + + o("promise functions", async function() { + await wrapPromise(function() { + o(a).equals(b) + o(a).equals(1)("a and b should be initialized") + }) + }) + }) }) From 39be9134f94f78da635a6ad86d1722f047326c01 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 8 Aug 2017 09:07:39 +0200 Subject: [PATCH 3/5] Support err as first argument for done callbacks in async tests --- ospec/ospec.js | 18 +++++++++--------- ospec/tests/test-ospec.js | 10 +++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ospec/ospec.js b/ospec/ospec.js index cac962a6..a757543f 100644 --- a/ospec/ospec.js +++ b/ospec/ospec.js @@ -87,7 +87,13 @@ module.exports = new function init(name) { var timeout = 0, delay = 200, s = new Date var isDone = false - function done() { + function done(err) { + if (err) { + if (err.message) record(err.message, err) + else record(err) + subjects.pop() + next() + } if (timeout !== undefined) { timeout = clearTimeout(timeout) if (delay !== Infinity) record(null) @@ -114,9 +120,7 @@ module.exports = new function init(name) { fn(done, function(t) {delay = t}) } catch (e) { - record(e.message, e) - subjects.pop() - next() + done(e) } if (timeout === 0) { startTimer() @@ -126,11 +130,7 @@ module.exports = new function init(name) { var p = fn() if (p && p.then) { startTimer() - p.then(done, e => { - record(e.message, e) - subjects.pop() - next() - }) + p.then(function() { done() }, done) } else { nextTickish(next) } diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index dfc3f7b0..69f2e1f1 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -153,10 +153,14 @@ o.spec("ospec", function() { var a = 0, b = 0 function wrapPromise(fn) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { callAsync(() => { - fn() - resolve() + try { + fn() + resolve() + } catch(e) { + reject(e) + } }) }) } From d8113a44706a3f9ebb8cad0162b20ca44ae53a7a Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 8 Aug 2017 09:12:25 +0200 Subject: [PATCH 4/5] Adapt change-log --- docs/change-log.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/change-log.md b/docs/change-log.md index 5b30bb0a..fa0fcd9d 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -23,6 +23,11 @@ - API: Introduction of `m.redraw.sync()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) +#### Ospec improvements: + +- Added support for async functions and promises in tests - ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928)) +- Error handling for async tests with `done` callbacks supports error as first argument + #### Bug fixes - API: `m.route.set()` causes all mount points to be redrawn ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) From 6fd8a52da0f4cc21815e5a20957cca8631afbdf8 Mon Sep 17 00:00:00 2001 From: Stephan Hoyer Date: Tue, 8 Aug 2017 20:57:34 +0200 Subject: [PATCH 5/5] Adapt test to new async callback behaviour --- promise/tests/test-promise.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/promise/tests/test-promise.js b/promise/tests/test-promise.js index 48a9fac3..e368165c 100644 --- a/promise/tests/test-promise.js +++ b/promise/tests/test-promise.js @@ -131,7 +131,7 @@ o.spec("promise", function() { callAsync(function() {resolve(promise)}) }) - promise.then(null, done) + promise.then(null, () => done()) }) o("non-function onFulfilled is ignored", function(done) { var promise = Promise.resolve(1)