diff --git a/.gitignore b/.gitignore
index 8281286..e6d3196 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ node_modules
*.obj
*.exe
*.user
+*.qmlc
Makefile*
moc_*.cpp
moc_*.h
diff --git a/include/QtQmlPromise b/include/QtQmlPromise
new file mode 100644
index 0000000..552a824
--- /dev/null
+++ b/include/QtQmlPromise
@@ -0,0 +1,6 @@
+#ifndef QTQMLPROMISE_MODULE_H
+#define QTQMLPROMISE_MODULE_H
+
+#include "../src/qtqmlpromise/qjspromise.h"
+
+#endif // ifndef QTQMLPROMISE_MODULE_H
diff --git a/qtpromise.pri b/qtpromise.pri
index d3302a8..a9a4a59 100644
--- a/qtpromise.pri
+++ b/qtpromise.pri
@@ -1,3 +1,11 @@
INCLUDEPATH += $$PWD/include $$PWD/src
DEPENDPATH += $$PWD/include $$PWD/src
CONFIG += c++11
+
+qtpromise-qml {
+ QML_IMPORT_PATH += $$shadowed($$PWD)/qml
+
+ # To avoid carrying an extra library dependency, the QJSPromise definition is
+ # embedded in the QML plugin, so we need to link against the plugin itself.
+ LIBS += -L$$shadowed($$PWD)/qml/QtPromise -l$$qtLibraryTarget(qtpromiseplugin)
+}
diff --git a/qtpromise.pro b/qtpromise.pro
index 2475104..b7d790e 100644
--- a/qtpromise.pro
+++ b/qtpromise.pro
@@ -1,10 +1,9 @@
TEMPLATE = subdirs
SUBDIRS = \
+ src \
tests
-_qt_creator_ {
- SUBDIRS += src
-}
+tests.depends = src
OTHER_FILES = \
package/features/*.prf \
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
new file mode 100644
index 0000000..eeb58d4
--- /dev/null
+++ b/src/imports/imports.pro
@@ -0,0 +1,25 @@
+TEMPLATE = lib
+CONFIG += plugin exceptions
+QT += qml
+
+IMPORT_VERSION = 1.0
+DEFINES += QTQMLPROMISE_LIBRARY
+
+TARGET = $$qtLibraryTarget(qtpromiseplugin)
+DESTDIR = $$shadowed($$PWD/../../qml/QtPromise)
+
+include(../qtqmlpromise/qtqmlpromise.pri)
+include(../../qtpromise.pri)
+
+SOURCES += \
+ $$PWD/plugin.cpp
+
+QMLFILES += \
+ $$PWD/plugins.qmltypes \
+ $$PWD/qmldir
+
+RESOURCES += $$PWD/imports.qrc
+
+qmlfiles.files = $$QMLFILES
+qmlfiles.path = $$DESTDIR
+COPIES += qmlfiles
diff --git a/src/imports/imports.qrc b/src/imports/imports.qrc
new file mode 100644
index 0000000..4a2a6f9
--- /dev/null
+++ b/src/imports/imports.qrc
@@ -0,0 +1,5 @@
+
+
+ qtqmlpromise.js
+
+
diff --git a/src/imports/plugin.cpp b/src/imports/plugin.cpp
new file mode 100644
index 0000000..0b33a4c
--- /dev/null
+++ b/src/imports/plugin.cpp
@@ -0,0 +1,76 @@
+// QtPromise
+#include
+
+// Qt
+#include
+#include
+
+static const char* kJSPromisePrivateNamespace = "__qtpromise_private__";
+
+using namespace QtPromise;
+
+class QtQmlPromiseObject : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtQmlPromiseObject(QJSEngine* engine)
+ : QObject(engine)
+ , m_engine(engine)
+ {
+ Q_ASSERT(engine);
+ }
+
+ Q_INVOKABLE QJSValue create(const QJSValue& resolver, const QJSValue& prototype)
+ {
+ QJSValue value = m_engine->toScriptValue(QJSPromise(m_engine, resolver));
+ value.setPrototype(prototype);
+ return value;
+ }
+
+ Q_INVOKABLE QtPromise::QJSPromise resolve(QJSValue value)
+ {
+ return QJSPromise::resolve(std::move(value));
+ }
+
+ Q_INVOKABLE QtPromise::QJSPromise reject(QJSValue error)
+ {
+ return QJSPromise::reject(std::move(error));
+ }
+
+ Q_INVOKABLE QtPromise::QJSPromise all(QJSValue input)
+ {
+ return QJSPromise::all(m_engine, std::move(input));
+ }
+
+private:
+ QJSEngine* m_engine;
+};
+
+class QtQmlPromisePlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+
+public:
+ void registerTypes(const char* uri) Q_DECL_OVERRIDE
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("QtPromise"));
+ Q_UNUSED(uri);
+
+ qRegisterMetaType();
+ }
+
+ void initializeEngine(QQmlEngine* engine, const char* uri) Q_DECL_OVERRIDE
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("QtPromise"));
+ Q_UNUSED(uri);
+
+ QJSValue global = engine->globalObject();
+ QJSValue object = engine->newQObject(new QtQmlPromiseObject(engine));
+ global.setProperty(kJSPromisePrivateNamespace, object);
+ }
+
+}; // class QtQmlPromisePlugin
+
+#include "plugin.moc"
diff --git a/src/imports/plugins.qmltypes b/src/imports/plugins.qmltypes
new file mode 100644
index 0000000..cd4e7a3
--- /dev/null
+++ b/src/imports/plugins.qmltypes
@@ -0,0 +1,5 @@
+import QtQuick.tooling 1.2
+
+Module {
+ dependencies: []
+}
diff --git a/src/imports/qmldir b/src/imports/qmldir
new file mode 100644
index 0000000..8bd4542
--- /dev/null
+++ b/src/imports/qmldir
@@ -0,0 +1,5 @@
+module QtPromise
+plugin qtpromiseplugin
+typeinfo plugins.qmltypes
+classname QtQmlPromisePlugin
+Promise 1.0 qrc:///QtPromise/qtqmlpromise.js
diff --git a/src/imports/qtqmlpromise.js b/src/imports/qtqmlpromise.js
new file mode 100644
index 0000000..5ded053
--- /dev/null
+++ b/src/imports/qtqmlpromise.js
@@ -0,0 +1,16 @@
+.pragma library
+
+(function(global) {
+ var private = global.__qtpromise_private__;
+ delete global.__qtpromise_private__;
+
+ var Promise = global.Promise = function(resolver) {
+ return private.create(function(proxy) {
+ resolver(proxy.resolve, proxy.reject);
+ }, this);
+ };
+
+ ['all', 'reject', 'resolve'].forEach(function(method) {
+ Promise[method] = private[method];
+ });
+})(this);
diff --git a/src/qtpromise/qtpromise.pro b/src/qtpromise/qtpromise.pro
index 71afe76..8b7e952 100644
--- a/src/qtpromise/qtpromise.pro
+++ b/src/qtpromise/qtpromise.pro
@@ -1,5 +1,5 @@
-TEMPLATE = lib
-CONFIG += c++11 qt thread warn_on
+TEMPLATE = aux
+CONFIG += c++11 force_qt thread warn_on
DEFINES += QT_DEPRECATED_WARNINGS
include(qtpromise.pri)
diff --git a/src/qtqmlpromise/qjspromise.cpp b/src/qtqmlpromise/qjspromise.cpp
new file mode 100644
index 0000000..eb53f38
--- /dev/null
+++ b/src/qtqmlpromise/qjspromise.cpp
@@ -0,0 +1,175 @@
+// QtQmlPromise
+#include "qjspromise.h"
+#include "qjspromise_p.h"
+
+// Qt
+#include
+#include
+
+using namespace QtPromise;
+
+namespace {
+
+QJSValue toJSArray(QJSEngine* engine, const QVector& values)
+{
+ Q_ASSERT(engine);
+
+ const int count = values.count();
+ QJSValue array = engine->newArray(count);
+
+ for (int i = 0; i < count; ++i) {
+ array.setProperty(i, values[i]);
+ }
+
+ return array;
+}
+
+template
+QJSValue newError(const QJSValue& source, const T& value)
+{
+ QJSEngine* engine = source.engine();
+ QJSValue error = engine->evaluate("throw new Error('')");
+ error.setProperty("message", engine->toScriptValue(value));
+ return error;
+}
+
+} // anonymous namespace
+
+QJSPromise::QJSPromise()
+ : m_promise(QPromise::resolve(QJSValue()))
+{ }
+
+QJSPromise::QJSPromise(QPromise&& promise)
+ : m_promise(std::move(promise))
+{
+}
+
+QJSPromise::QJSPromise(QJSEngine* engine, QJSValue resolver)
+ : m_promise([=](
+ const QPromiseResolve& resolve,
+ const QPromiseReject& reject) mutable {
+
+ // resolver is part of the Promise wrapper in qtqmlpromise.js
+ Q_ASSERT(resolver.isCallable());
+ Q_ASSERT(engine);
+
+ auto proxy = QtPromisePrivate::JSPromiseResolver(resolve, reject);
+ auto ret = resolver.call(QJSValueList() << engine->toScriptValue(proxy));
+ if (ret.isError()) {
+ throw ret;
+ }
+ })
+{ }
+
+QJSPromise QJSPromise::then(QJSValue fulfilled, QJSValue rejected) const
+{
+ const bool fulfillable = fulfilled.isCallable();
+ const bool rejectable = rejected.isCallable();
+
+ if (!fulfillable && !rejectable) {
+ return *this;
+ }
+
+ auto _rejected = [=]() mutable {
+ QJSValue error;
+
+ try {
+ throw;
+ } catch (const QJSValue& err) {
+ error = err;
+ } catch (const QVariant& err) {
+ error = newError(rejected, err);
+ } catch (const std::exception& e) {
+ error = newError(rejected, QString(e.what()));
+ } catch (...) {
+ error = newError(rejected, QString("Unknown error"));
+ }
+
+ return rejected.call({error});
+ };
+
+ if (!fulfillable) {
+ return m_promise.then(nullptr, _rejected);
+ }
+
+ auto _fulfilled = [=](const QJSValue& res) mutable {
+ return fulfilled.call(QJSValueList() << res);
+ };
+
+ if (!rejectable) {
+ return m_promise.then(_fulfilled);
+ }
+
+ return m_promise.then(_fulfilled, _rejected);
+}
+
+QJSPromise QJSPromise::fail(QJSValue handler) const
+{
+ return then(QJSValue(), handler);
+}
+
+QJSPromise QJSPromise::finally(QJSValue handler) const
+{
+ return m_promise.finally([=]() mutable {
+ return handler.call();
+ });
+}
+
+QJSPromise QJSPromise::tap(QJSValue handler) const
+{
+ return m_promise.tap([=](const QJSValue& res) mutable {
+ return handler.call(QJSValueList() << res);
+ });
+}
+
+QJSPromise QJSPromise::delay(int msec) const
+{
+ return m_promise.delay(msec);
+}
+
+QJSPromise QJSPromise::wait() const
+{
+ return m_promise.wait();
+}
+
+QJSPromise QJSPromise::resolve(QJSValue&& value)
+{
+ return QPromise::resolve(std::forward(value));
+}
+
+QJSPromise QJSPromise::reject(QJSValue&& error)
+{
+ return QPromise::reject(std::forward(error));
+}
+
+QJSPromise QJSPromise::all(QJSEngine* engine, QJSValue&& input)
+{
+ Q_ASSERT(engine);
+
+ if (!input.isArray()) {
+ // TODO TYPEERROR!
+ return QPromise::reject("foobar");
+ }
+
+ Q_ASSERT(input.hasProperty("length"));
+ const int count = input.property("length").toInt();
+ if (!count) {
+ return QPromise::resolve(QJSValue(input));
+ }
+
+ QList > promises;
+ for (int i = 0; i < count; ++i) {
+ QJSValue value = input.property(i);
+ const QVariant variant = value.toVariant();
+ if (variant.userType() == qMetaTypeId()) {
+ promises << variant.value().m_promise;
+ } else {
+ promises << QPromise::resolve(std::move(value));
+ }
+ }
+
+ return QPromise::all(promises)
+ .then([engine](const QVector& results) {
+ return toJSArray(engine, results);
+ });
+}
diff --git a/src/qtqmlpromise/qjspromise.h b/src/qtqmlpromise/qjspromise.h
new file mode 100644
index 0000000..963a9c4
--- /dev/null
+++ b/src/qtqmlpromise/qjspromise.h
@@ -0,0 +1,52 @@
+#ifndef QTQMLPROMISE_QJSPROMISE_H
+#define QTQMLPROMISE_QJSPROMISE_H
+
+// QtQmlPromise
+#include "qtqmlpromiseglobal.h"
+
+// QtPromise
+#include
+
+// Qt
+#include
+
+namespace QtPromise {
+
+class QTQMLPROMISE_EXPORT QJSPromise
+{
+ Q_GADGET
+
+public:
+ QJSPromise();
+ QJSPromise(QJSEngine* engine, QJSValue resolver);
+ QJSPromise(QPromise&& promise);
+
+ Q_INVOKABLE bool isFulfilled() const { return m_promise.isFulfilled(); }
+ Q_INVOKABLE bool isRejected() const { return m_promise.isRejected(); }
+ Q_INVOKABLE bool isPending() const{ return m_promise.isPending(); }
+
+ Q_INVOKABLE QtPromise::QJSPromise then(QJSValue fulfilled, QJSValue rejected = QJSValue()) const;
+ Q_INVOKABLE QtPromise::QJSPromise fail(QJSValue handler) const;
+ Q_INVOKABLE QtPromise::QJSPromise finally(QJSValue handler) const;
+ Q_INVOKABLE QtPromise::QJSPromise tap(QJSValue handler) const;
+ Q_INVOKABLE QtPromise::QJSPromise delay(int msec) const;
+ Q_INVOKABLE QtPromise::QJSPromise wait() const;
+
+public: // STATICS
+ static QJSPromise resolve(QJSValue&& value);
+ static QJSPromise reject(QJSValue&& error);
+ static QJSPromise all(QJSEngine* engine, QJSValue&& input);
+
+private:
+ friend struct QtPromisePrivate::PromiseFulfill;
+
+ QPromise m_promise;
+
+}; // class QJSPromise
+
+} // namespace QtPromise
+
+Q_DECLARE_TYPEINFO(QtPromise::QJSPromise, Q_MOVABLE_TYPE);
+Q_DECLARE_METATYPE(QtPromise::QJSPromise)
+
+#endif // ifndef QTQMLPROMISE_QJSPROMISE_H
diff --git a/src/qtqmlpromise/qjspromise_p.h b/src/qtqmlpromise/qjspromise_p.h
new file mode 100644
index 0000000..26cd6da
--- /dev/null
+++ b/src/qtqmlpromise/qjspromise_p.h
@@ -0,0 +1,71 @@
+#ifndef QTQMLPROMISE_QJSPROMISE_P_H
+#define QTQMLPROMISE_QJSPROMISE_P_H
+
+// QtQmlPromise
+#include "qjspromise.h"
+
+// QtPromise
+#include
+
+// Qt
+#include
+
+namespace QtPromisePrivate {
+
+template <>
+struct PromiseFulfill
+{
+ static void call(
+ const QJSValue& value,
+ const QtPromise::QPromiseResolve& resolve,
+ const QtPromise::QPromiseReject& reject)
+ {
+ using namespace QtPromise;
+
+ if (value.isObject()) {
+ const QVariant variant = value.toVariant();
+ if (variant.userType() == qMetaTypeId()) {
+ const auto promise = variant.value().m_promise;
+ PromiseFulfill >::call(promise, resolve, reject);
+ return;
+ }
+ }
+
+ if (value.isError()) {
+ reject(value);
+ } else {
+ resolve(value);
+ }
+ }
+};
+
+class JSPromiseResolver
+{
+ Q_GADGET
+
+public:
+ JSPromiseResolver() {}
+ JSPromiseResolver(
+ const QtPromise::QPromiseResolve& resolve,
+ const QtPromise::QPromiseReject& reject)
+ : m_d(new Data{resolve, reject})
+ { }
+
+ Q_INVOKABLE void resolve(QJSValue value = QJSValue()) { m_d->resolve(std::move(value)); }
+ Q_INVOKABLE void reject(QJSValue error = QJSValue()) { m_d->reject(std::move(error)); }
+
+private:
+ struct Data
+ {
+ QtPromise::QPromiseResolve resolve;
+ QtPromise::QPromiseReject reject;
+ }; // struct Data
+
+ QSharedPointer m_d;
+};
+
+} // namespace QtPromisePrivate
+
+Q_DECLARE_METATYPE(QtPromisePrivate::JSPromiseResolver)
+
+#endif // ifndef QTQMLPROMISE_QJSPROMISE_P_H
diff --git a/src/qtqmlpromise/qtqmlpromise.pri b/src/qtqmlpromise/qtqmlpromise.pri
new file mode 100644
index 0000000..d80922e
--- /dev/null
+++ b/src/qtqmlpromise/qtqmlpromise.pri
@@ -0,0 +1,7 @@
+HEADERS += \
+ $$PWD/qjspromise.h \
+ $$PWD/qjspromise_p.h \
+ $$PWD/qtqmlpromiseglobal.h
+
+SOURCES += \
+ $$PWD/qjspromise.cpp
diff --git a/src/qtqmlpromise/qtqmlpromise.pro b/src/qtqmlpromise/qtqmlpromise.pro
new file mode 100644
index 0000000..61f306d
--- /dev/null
+++ b/src/qtqmlpromise/qtqmlpromise.pro
@@ -0,0 +1,8 @@
+TEMPLATE = aux
+CONFIG += c++11 force_qt thread warn_on
+DEFINES += QT_DEPRECATED_WARNINGS
+DEFINES += QTQMLPROMISE_LIBRARY
+QT += qml
+
+include($$PWD/../../qtpromise.pri)
+include($$PWD/qtqmlpromise.pri)
diff --git a/src/qtqmlpromise/qtqmlpromiseglobal.h b/src/qtqmlpromise/qtqmlpromiseglobal.h
new file mode 100644
index 0000000..10f7471
--- /dev/null
+++ b/src/qtqmlpromise/qtqmlpromiseglobal.h
@@ -0,0 +1,16 @@
+#ifndef QTQMLPROMISE_QTQMLPROMISEGLOBAL_H
+#define QTQMLPROMISE_QTQMLPROMISEGLOBAL_H
+
+// QtPromise
+#include
+
+// QtCore
+#include
+
+#ifdef QTQMLPROMISE_LIBRARY
+# define QTQMLPROMISE_EXPORT Q_DECL_EXPORT
+#else
+# define QTQMLPROMISE_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // ifndef QTQMLPROMISE_QTQMLPROMISEGLOBAL_H
diff --git a/src/src.pro b/src/src.pro
index 720a606..4f1a84d 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -1,2 +1,5 @@
TEMPLATE = subdirs
-SUBDIRS = qtpromise
+SUBDIRS += \
+ imports \
+ qtpromise \
+ qtqmlpromise
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index bfff61e..c3500fa 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,2 +1,4 @@
TEMPLATE = subdirs
-SUBDIRS += qtpromise
+SUBDIRS += \
+ qtpromise \
+ qtqmlpromise
diff --git a/tests/auto/qtqmlpromise/extension/extension.pro b/tests/auto/qtqmlpromise/extension/extension.pro
new file mode 100644
index 0000000..a0f19f9
--- /dev/null
+++ b/tests/auto/qtqmlpromise/extension/extension.pro
@@ -0,0 +1,5 @@
+TARGET = tst_qtqmlpromise_extension
+SOURCES += $$PWD/tst_extension.cpp
+OTHER_FILES += $$PWD/*.qml
+
+include(../qtqmlpromise.pri)
diff --git a/tests/auto/qtqmlpromise/extension/tst_extension.cpp b/tests/auto/qtqmlpromise/extension/tst_extension.cpp
new file mode 100644
index 0000000..ae393e5
--- /dev/null
+++ b/tests/auto/qtqmlpromise/extension/tst_extension.cpp
@@ -0,0 +1,10 @@
+#include
+
+static void initialize()
+{
+ qputenv("QML2_IMPORT_PATH", QTPROMISE_IMPORTPATH);
+}
+
+Q_COREAPP_STARTUP_FUNCTION(initialize)
+
+QUICK_TEST_MAIN(extension)
diff --git a/tests/auto/qtqmlpromise/extension/tst_extension.qml b/tests/auto/qtqmlpromise/extension/tst_extension.qml
new file mode 100644
index 0000000..892c8d4
--- /dev/null
+++ b/tests/auto/qtqmlpromise/extension/tst_extension.qml
@@ -0,0 +1,39 @@
+import QtQuick 2.3
+import QtPromise 1.0
+import QtTest 1.0
+
+TestCase {
+ name: "Extension"
+
+ function test_global() {
+ compare(typeof __qtpromise_private__, 'undefined');
+ compare(typeof Promise, 'function');
+ compare(typeof Promise.resolve, 'function');
+ compare(typeof Promise.reject, 'function');
+ }
+
+ function test_instance() {
+ var p = new Promise(function() {});
+ compare(Object.prototype.toString(p), '[object Object]');
+ compare(p instanceof Promise, true);
+ compare(typeof p, 'object');
+ }
+
+ function test_prototype() {
+ var p = new Promise(function() {});
+
+ [
+ 'delay',
+ 'fail',
+ 'finally',
+ 'isFulfilled',
+ 'isRejected',
+ 'isPending',
+ 'tap',
+ 'then',
+ 'wait',
+ ].forEach(function(name) {
+ compare(typeof p[name], 'function');
+ });
+ }
+}
diff --git a/tests/auto/qtqmlpromise/qtqmlpromise.pri b/tests/auto/qtqmlpromise/qtqmlpromise.pri
new file mode 100644
index 0000000..7b9fb7b
--- /dev/null
+++ b/tests/auto/qtqmlpromise/qtqmlpromise.pri
@@ -0,0 +1,9 @@
+TEMPLATE = app
+CONFIG += qmltestcase
+CONFIG += qtpromise-qml
+DEFINES += QT_DEPRECATED_WARNINGS
+
+QTPROMISE_IMPORTPATH = $$shadowed($$PWD/../../../qml)
+DEFINES += QTPROMISE_IMPORTPATH=\"\\\"$$QTPROMISE_IMPORTPATH\\\"\"
+
+include(../../../qtpromise.pri)
diff --git a/tests/auto/qtqmlpromise/qtqmlpromise.pro b/tests/auto/qtqmlpromise/qtqmlpromise.pro
new file mode 100644
index 0000000..431995f
--- /dev/null
+++ b/tests/auto/qtqmlpromise/qtqmlpromise.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+SUBDIRS += \
+ extension \
+ requirements
diff --git a/tests/auto/qtqmlpromise/requirements/requirements.pro b/tests/auto/qtqmlpromise/requirements/requirements.pro
new file mode 100644
index 0000000..dc504ef
--- /dev/null
+++ b/tests/auto/qtqmlpromise/requirements/requirements.pro
@@ -0,0 +1,5 @@
+TARGET = tst_qtqmlpromise_requirements
+SOURCES += $$PWD/tst_requirements.cpp
+OTHER_FILES += $$PWD/*.qml
+
+include(../qtqmlpromise.pri)
diff --git a/tests/auto/qtqmlpromise/requirements/tst_requirements.cpp b/tests/auto/qtqmlpromise/requirements/tst_requirements.cpp
new file mode 100644
index 0000000..3c59f74
--- /dev/null
+++ b/tests/auto/qtqmlpromise/requirements/tst_requirements.cpp
@@ -0,0 +1,10 @@
+#include
+
+static void initialize()
+{
+ qputenv("QML2_IMPORT_PATH", QTPROMISE_IMPORTPATH);
+}
+
+Q_COREAPP_STARTUP_FUNCTION(initialize)
+
+QUICK_TEST_MAIN(requirements)
diff --git a/tests/auto/qtqmlpromise/requirements/tst_requirements.qml b/tests/auto/qtqmlpromise/requirements/tst_requirements.qml
new file mode 100644
index 0000000..26824c1
--- /dev/null
+++ b/tests/auto/qtqmlpromise/requirements/tst_requirements.qml
@@ -0,0 +1,384 @@
+import QtQuick 2.3
+import QtPromise 1.0
+import QtTest 1.0
+
+// https://promisesaplus.com/#requirements
+TestCase {
+ name: "Requirements"
+
+ // 2.1. Promise States
+
+ // 2.1.1 When pending, a promise:
+ function test_pendingFulfilled() {
+ // 2.1.1.1. may transition to either the fulfilled state
+ var p = new Promise(function(resolve) {
+ Qt.callLater(function() {
+ resolve(42);
+ });
+ });
+
+ compare(p.isPending(), true);
+ compare(p.isFulfilled(), false);
+ compare(p.isRejected(), false);
+
+ p.wait();
+
+ compare(p.isPending(), false);
+ compare(p.isFulfilled(), true);
+ compare(p.isRejected(), false);
+ }
+
+ // 2.1.1 When pending, a promise:
+ function test_pendingRejected() {
+ // 2.1.1.1. ... or the rejected state
+ var p = new Promise(function(resolve, reject) {
+ Qt.callLater(function() {
+ reject(new Error(42));
+ });
+ });
+
+ compare(p.isPending(), true);
+ compare(p.isFulfilled(), false);
+ compare(p.isRejected(), false);
+
+ p.wait();
+
+ compare(p.isPending(), false);
+ compare(p.isFulfilled(), false);
+ compare(p.isRejected(), true);
+ }
+
+ // 2.1.2. When fulfilled, a promise:
+ function test_fulfilled() {
+ var p = new Promise(function(resolve, reject) {
+ Qt.callLater(function() {
+ // 2.1.2.2. must have a value, which must not change.
+ resolve(42);
+ resolve(43);
+
+ // 2.1.2.1. must not transition to any other state.
+ reject(new Error("foo"));
+ });
+ });
+
+ compare(p.isPending(), true);
+
+ var value = -1;
+ var error = null;
+
+ p.then(function(res) {
+ value = res;
+ }, function(err) {
+ error = err;
+ }).wait();
+
+ compare(p.isFulfilled(), true);
+ compare(p.isRejected(), false);
+ compare(error, null);
+ compare(value, 42);
+ }
+
+ // 2.1.3 When rejected, a promise:
+ function test_rejected() {
+ var p = new Promise(function(resolve, reject) {
+ Qt.callLater(function() {
+ // 2.1.3.2. must have a reason, which must not change.
+ reject(new Error("foo"));
+ reject(new Error("bar"));
+
+ // 2.1.3.1. must not transition to any other state.
+ resolve(42);
+ });
+ });
+
+ compare(p.isPending(), true);
+
+ var value = -1;
+ var error = null;
+
+ p.then(function(res) {
+ value = res;
+ }, function(err) {
+ error = err;
+ }).wait();
+
+ compare(p.isFulfilled(), false);
+ compare(p.isRejected(), true);
+ compare(error instanceof Error, true);
+ compare(error.message, 'foo');
+ compare(value, -1);
+ }
+
+ // 2.2. The then Method
+
+ // 2.2.1. Both onFulfilled and onRejected are given (resolve)
+ function test_thenArgsBothResolve() {
+ var error = null;
+ var value = -1;
+
+ Promise.resolve(42).then(
+ function(res) { value = res; },
+ function(err) { error = err; }
+ ).wait();
+
+ compare(error, null);
+ compare(value, 42);
+ }
+
+ // 2.2.1. Both onFulfilled and onRejected are given (reject)
+ function test_thenArgsBothReject() {
+ var error = null;
+ var value = -1;
+
+ Promise.reject(new Error('foo')).then(
+ function(res) { value = res; },
+ function(err) { error = err; }
+ ).wait();
+
+ compare(error instanceof Error, true);
+ compare(error.message, 'foo');
+ compare(value, -1);
+ }
+
+ // 2.2.1. onFulfilled is an optional arguments:
+ function test_thenArgsSecond() {
+ var error = null;
+
+ Promise.reject(new Error('foo')).then(
+ null,
+ function(err) { error = err; }
+ ).wait();
+
+ compare(error instanceof Error, true);
+ compare(error.message, 'foo');
+ }
+
+ // 2.2.1. onRejected is an optional arguments:
+ function test_thenArgsFirst() {
+ var value = -1;
+
+ Promise.resolve(42).then(
+ function(res) { value = res; }
+ ).wait();
+
+ compare(value, 42);
+ }
+
+ // 2.2.1.1. If onFulfilled is not a function, it must be ignored.
+ function test_thenArgsFirstInvalid() {
+ var value = -1;
+
+ Promise.resolve(42).then('invalid').then(
+ function(res) { value = res; }
+ ).wait();
+
+ compare(value, 42);
+ }
+
+ // 2.2.1.2. If onRejected is not a function, it must be ignored.
+ function test_thenArgsSecondInvalid() {
+ var error = -1;
+
+ Promise.reject(new Error('foo')).then(
+ null,
+ 'invalid'
+ ).then(
+ null,
+ function(err) { error = err; }
+ ).wait();
+
+ compare(error instanceof Error, true);
+ compare(error.message, 'foo');
+ }
+
+ // 2.2.2. If onFulfilled is a function:
+ function test_thenOnFulfilled() {
+ var p0 = new Promise(function(resolve) {
+ Qt.callLater(function() {
+ // 2.2.2.3. it must not be called more than once
+ resolve(42);
+ resolve(43);
+ });
+ });
+
+ var values = [];
+ var p1 = p0.then(function(res) {
+ values.push(res);
+ });
+
+ // 2.2.2.2. it must not be called before promise is fulfilled.
+ compare(p0.isPending(), true);
+ compare(p1.isPending(), true);
+ compare(values.length, 0);
+
+ p1.wait();
+
+ // 2.2.2.1. it must be called after promise is fulfilled,
+ // with promise’s value as its first argument.
+ compare(p0.isFulfilled(), true);
+ compare(p1.isFulfilled(), true);
+ compare(values, [42]);
+ }
+
+ // 2.2.3. If onRejected is a function:
+ function test_thenOnRejected() {
+ var p0 = new Promise(function(resolve, reject) {
+ Qt.callLater(function() {
+ // 2.2.2.3. it must not be called more than once
+ reject(new Error('foo'));
+ reject(new Error('bar'));
+ });
+ });
+
+ var errors = [];
+ var p1 = p0.then(null, function(res) {
+ errors.push(res);
+ });
+
+ // 2.2.3.2. it must not be called before promise is rejected.
+ compare(p0.isPending(), true);
+ compare(p1.isPending(), true);
+ compare(errors.length, 0);
+
+ p1.wait();
+
+ // 2.2.3.1. it must be called after promise is rejected,
+ // with promise’s reason as its first argument.
+ compare(p0.isRejected(), true);
+ compare(p1.isFulfilled(), true);
+ compare(errors.length, 1);
+ compare(errors[0] instanceof Error, true);
+ compare(errors[0].message, 'foo');
+ }
+
+ // 2.2.4. onFulfilled or onRejected must not be called until the execution context
+ // stack contains only platform code (ie. executed asynchronously, after the event
+ // loop turn in which then is called, and with a fresh stack).
+ function test_thenAsynchronous()
+ {
+ var value = -1;
+ var p0 = Promise.resolve(42);
+ var p1 = p0.then(function(res){ value = res; });
+
+ compare(p0.isFulfilled(), true);
+ compare(p1.isPending(), true);
+ compare(value, -1);
+
+ p1.wait();
+
+ compare(p1.isFulfilled(), true);
+ compare(value, 42);
+ }
+
+ // 2.2.5 onFulfilled and onRejected must be called as functions (i.e. with no this value).
+ function test_thenThisArg() {
+ var scopes = [];
+
+ Promise.resolve(42).then(function() { scopes.push(this); }).wait();
+ Promise.reject(new Error('foo')).then(null, function() { scopes.push(this); }).wait();
+
+ // Qt doesn't allow to call JS function with undefined "this"
+ // Let's adopt the sloppy mode (this === the global object).
+ var global = (function() { return this; })();
+ compare(scopes, [global, global]);
+ }
+
+ // 2.2.6. then may be called multiple times on the same promise:
+
+ // 2.2.6.1. If/when promise is fulfilled, all respective onFulfilled callbacks
+ // must execute in the order of their originating calls to then:
+ function test_thenMultipleCalls() {
+ var values = [];
+ var p = new Promise(function(resolve) {
+ Qt.callLater(function() {
+ resolve(42);
+ });
+ });
+
+ Promise.all([
+ p.then(function(res) { return res + 1; }),
+ p.then(function(res) { return res + 2; }),
+ p.then(function(res) { return res + 3; })
+ ]).then(function(res) {
+ values = res;
+ }).wait();
+
+ compare(values, [43, 44, 45]);
+ }
+}
+
+/*
+void tst_requirements::thenMultipleCalls()
+{
+ // 2.2.6.2. If/when promise is rejected, all respective onRejected callbacks
+ // must execute in the order of their originating calls to then:
+ {
+ QVector values;
+ QPromise p([](const QPromiseResolve&, const QPromiseReject& reject) {
+ qtpromise_defer([=]() {
+ reject(8);
+ });
+ });
+
+ qPromiseAll(QVector >{
+ p.then(nullptr, [&](int r) { values << r + 1; return r + 1; }),
+ p.then(nullptr, [&](int r) { values << r + 2; return r + 2; }),
+ p.then(nullptr, [&](int r) { values << r + 3; return r + 3; })
+ }).wait();
+
+ QCOMPARE(values, QVector({9, 10, 11}));
+ }
+}
+
+void tst_requirements::thenHandlers()
+{
+ // 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
+ {
+ auto handler = [](){ return 42; };
+ auto p1 = QPromise::resolve(42);
+ Q_STATIC_ASSERT((std::is_same >::value));
+ Q_STATIC_ASSERT((std::is_same >::value));
+ Q_STATIC_ASSERT((std::is_same >::value));
+ }
+
+ // 2.2.7.1. If either onFulfilled or onRejected returns a value x, run the
+ // Promise Resolution Procedure [[Resolve]](p2, x) -> See 2.3.
+
+ // 2.2.7.2. If either onFulfilled or onRejected throws an exception e,
+ // p2 must be rejected with e as the reason.
+ {
+ QString reason;
+ auto p1 = QPromise::resolve(42);
+ auto p2 = p1.then([](){ throw QString("foo"); });
+ p2.then(nullptr, [&](const QString& e) { reason = e; }).wait();
+
+ QVERIFY(p1.isFulfilled());
+ QVERIFY(p2.isRejected());
+ QCOMPARE(reason, QString("foo"));
+ }
+ {
+ QString reason;
+ auto p1 = QPromise::reject(QString("foo"));
+ auto p2 = p1.then(nullptr, [](){ throw QString("bar"); return 42; });
+ p2.then(nullptr, [&](const QString& e) { reason = e; return 0; }).wait();
+
+ QVERIFY(p1.isRejected());
+ QVERIFY(p2.isRejected());
+ QCOMPARE(reason, QString("bar"));
+ }
+
+ // 2.2.7.3. If onFulfilled is not a function and promise1 is fulfilled,
+ // p2 must be fulfilled with the same value as promise1
+ {
+ QString value;
+ auto p1 = QPromise::resolve("42");
+ auto p2 = p1.then(nullptr, [](){ return QString(); });
+ Q_STATIC_ASSERT((std::is_same >::value));
+ p2.then([&](const QString& e) { value = e; }).wait();
+
+ QVERIFY(p1.isFulfilled());
+ QVERIFY(p2.isFulfilled());
+ QCOMPARE(value, QString("42"));
+ }
+}
+*/