2017-05-15 01:03:01 +08:00
|
|
|
|
// QtPromise
|
2017-05-25 15:19:36 +08:00
|
|
|
|
#include <QtPromise>
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// Qt
|
|
|
|
|
#include <QtTest>
|
|
|
|
|
|
|
|
|
|
using namespace QtPromise;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
using namespace QtPromisePrivate;
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// https://promisesaplus.com/#requirements
|
|
|
|
|
class tst_requirements: public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
private Q_SLOTS:
|
|
|
|
|
|
|
|
|
|
// 2.1. Promise States
|
|
|
|
|
void statePending(); // 2.1.1.
|
|
|
|
|
void stateFulfilled(); // 2.1.2.
|
|
|
|
|
void stateRejected(); // 2.1.3.
|
|
|
|
|
|
|
|
|
|
// 2.2. The then Method
|
|
|
|
|
void thenArguments(); // 2.2.1.
|
|
|
|
|
void thenOnFulfilled(); // 2.2.2.
|
|
|
|
|
void thenOnRejected(); // 2.2.3.
|
|
|
|
|
void thenAsynchronous(); // 2.2.4.
|
|
|
|
|
// 2.2.5. NOT APPLICABLE
|
|
|
|
|
void thenMultipleCalls(); // 2.2.6.
|
|
|
|
|
void thenHandlers(); // 2.2.7.
|
|
|
|
|
|
|
|
|
|
// 2.3. The Promise Resolution Procedure
|
|
|
|
|
|
|
|
|
|
}; // class tst_requirements
|
|
|
|
|
|
|
|
|
|
QTEST_MAIN(tst_requirements)
|
|
|
|
|
#include "tst_requirements.moc"
|
|
|
|
|
|
|
|
|
|
void tst_requirements::statePending()
|
|
|
|
|
{
|
|
|
|
|
// 2.1.1. When pending, a promise:
|
2017-05-20 15:40:42 +08:00
|
|
|
|
// 2.1.1.1. may transition to either the fulfilled state
|
2017-05-15 01:03:01 +08:00
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p([&](const QPromiseResolve<int>& resolve) {
|
|
|
|
|
qtpromise_defer([=]() { resolve(42); });
|
|
|
|
|
});
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p.isPending());
|
|
|
|
|
QVERIFY(!p.isFulfilled());
|
|
|
|
|
QVERIFY(!p.isRejected());
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
p.wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(!p.isPending());
|
|
|
|
|
QVERIFY(p.isFulfilled());
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(!p.isRejected());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2.1.1.1. ... or the rejected state
|
2017-05-20 15:40:42 +08:00
|
|
|
|
{
|
|
|
|
|
QPromise<int> p([&](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
|
|
|
|
qtpromise_defer([=]() { reject(QString("foo")); });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QVERIFY(p.isPending());
|
|
|
|
|
QVERIFY(!p.isFulfilled());
|
|
|
|
|
QVERIFY(!p.isRejected());
|
|
|
|
|
|
|
|
|
|
p.wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(!p.isPending());
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(!p.isFulfilled());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p.isRejected());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::stateFulfilled()
|
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QString error;
|
|
|
|
|
int value = -1;
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// 2.1.2. When fulfilled, a promise:
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p([](
|
|
|
|
|
const QPromiseResolve<int>& resolve,
|
|
|
|
|
const QPromiseReject<int>& reject) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
// 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(QString("foo"));
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(p.isPending());
|
|
|
|
|
|
|
|
|
|
p.then([&](int res) {
|
|
|
|
|
value = res;
|
|
|
|
|
}, [&](const QString& err) {
|
|
|
|
|
error = err;
|
|
|
|
|
}).wait();
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
QVERIFY(p.isFulfilled());
|
|
|
|
|
QVERIFY(!p.isRejected());
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(error.isEmpty());
|
|
|
|
|
QCOMPARE(value, 42);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::stateRejected()
|
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QString error;
|
|
|
|
|
int value = -1;
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// 2.1.3 When rejected, a promise:
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p([](
|
|
|
|
|
const QPromiseResolve<int>& resolve,
|
|
|
|
|
const QPromiseReject<int>& reject) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
// 2.1.3.2. must have a reason, which must not change.
|
|
|
|
|
reject(QString("foo"));
|
|
|
|
|
reject(QString("bar"));
|
|
|
|
|
|
|
|
|
|
// 2.1.3.1. must not transition to any other state.
|
|
|
|
|
resolve(42);
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(p.isPending());
|
|
|
|
|
|
|
|
|
|
p.then([&](int res) {
|
|
|
|
|
value = res;
|
|
|
|
|
}, [&](const QString& err) {
|
|
|
|
|
error = err;
|
|
|
|
|
}).wait();
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
QVERIFY(!p.isFulfilled());
|
|
|
|
|
QVERIFY(p.isRejected());
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QCOMPARE(error, QString("foo"));
|
|
|
|
|
QCOMPARE(value, -1);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenArguments()
|
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
// 2.2.1. Both onFulfilled and onRejected are given
|
2017-05-15 01:03:01 +08:00
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QString error;
|
|
|
|
|
int value = -1;
|
|
|
|
|
QPromise<int>::resolve(42).then(
|
|
|
|
|
[&](int res) { value = res; },
|
|
|
|
|
[&](const QString& err) { error = err; }
|
|
|
|
|
).wait();
|
|
|
|
|
|
|
|
|
|
QVERIFY(error.isEmpty());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
QString error;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
int value = -1;
|
|
|
|
|
QPromise<int>::reject(QString("foo")).then(
|
|
|
|
|
[&](int res) { value = res; },
|
|
|
|
|
[&](const QString& err){ error = err; }
|
|
|
|
|
).wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(error, QString("foo"));
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QCOMPARE(value, -1);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2.2.1. onFulfilled is an optional arguments:
|
|
|
|
|
{
|
|
|
|
|
QString error;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int>::reject(QString("foo")).then(
|
|
|
|
|
nullptr,
|
|
|
|
|
[&](const QString& err){ error = err; return 42; }
|
|
|
|
|
).wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(error, QString("foo"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2.2.1. onRejected is an optional arguments:
|
|
|
|
|
{
|
2017-05-20 15:40:42 +08:00
|
|
|
|
int value = -1;
|
|
|
|
|
QPromise<int>::resolve(42).then(
|
|
|
|
|
[&value](int res) { value = res; }
|
|
|
|
|
).wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2.2.1.1. If onFulfilled is not a function, it must be ignored.
|
|
|
|
|
// 2.2.1.2. If onRejected is not a function, it must be ignored.
|
|
|
|
|
// -> compilation fails on type check.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenOnFulfilled()
|
|
|
|
|
{
|
|
|
|
|
// 2.2.2. If onFulfilled is a function:
|
|
|
|
|
QVector<int> values;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p0([](const QPromiseResolve<int>& resolve) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
// 2.2.2.3. it must not be called more than once
|
|
|
|
|
resolve(42);
|
|
|
|
|
resolve(43);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto p1 = p0.then([&](int res) { values << res; });
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// 2.2.2.2. it must not be called before promise is fulfilled.
|
|
|
|
|
QVERIFY(p0.isPending());
|
|
|
|
|
QVERIFY(p1.isPending());
|
|
|
|
|
QVERIFY(values.isEmpty());
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
p1.wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
// 2.2.2.1. it must be called after promise is fulfilled,
|
|
|
|
|
// with promise’s value as its first argument.
|
|
|
|
|
QVERIFY(p0.isFulfilled());
|
|
|
|
|
QVERIFY(p1.isFulfilled());
|
|
|
|
|
QCOMPARE(values, QVector<int>({42}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenOnRejected()
|
|
|
|
|
{
|
|
|
|
|
// 2.2.3. If onRejected is a function:
|
|
|
|
|
QStringList errors;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<void> p0([](const QPromiseResolve<void>&, const QPromiseReject<void>& reject) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
// 2.2.3.3. it must not be called more than once.
|
|
|
|
|
reject(QString("foo"));
|
|
|
|
|
reject(QString("bar"));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto p1 = p0.then(nullptr, [&](const QString& err) { errors << err; });
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
// 2.2.3.2. it must not be called before promise is rejected.
|
|
|
|
|
QVERIFY(p0.isPending());
|
|
|
|
|
QVERIFY(p1.isPending());
|
|
|
|
|
QVERIFY(errors.isEmpty());
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
p1.wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
// 2.2.3.1. it must be called after promise is rejected,
|
|
|
|
|
// with promise’s reason as its first argument.
|
|
|
|
|
QVERIFY(p0.isRejected());
|
|
|
|
|
QVERIFY(p1.isFulfilled());
|
|
|
|
|
QCOMPARE(errors, QStringList({"foo"}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenAsynchronous()
|
|
|
|
|
{
|
|
|
|
|
// 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).
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
int value = -1;
|
|
|
|
|
auto p0 = QPromise<int>::resolve(42);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p0.isFulfilled());
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
auto p1 = p0.then([&](int res){ value = res; });
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p1.isPending());
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QCOMPARE(value, -1);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
|
|
|
|
p1.wait();
|
|
|
|
|
QVERIFY(p1.isFulfilled());
|
|
|
|
|
QCOMPARE(value, 42);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenMultipleCalls()
|
|
|
|
|
{
|
|
|
|
|
// 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:
|
|
|
|
|
{
|
|
|
|
|
QVector<int> values;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p([](const QPromiseResolve<int>& resolve) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
resolve(42);
|
|
|
|
|
});
|
2017-05-15 01:03:01 +08:00
|
|
|
|
});
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
qPromiseAll(QVector<QPromise<void> >{
|
|
|
|
|
p.then([&](int r) { values << r + 1; }),
|
|
|
|
|
p.then([&](int r) { values << r + 2; }),
|
|
|
|
|
p.then([&](int r) { values << r + 3; })
|
|
|
|
|
}).wait();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(values, QVector<int>({43, 44, 45}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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<int> values;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QPromise<int> p([](const QPromiseResolve<int>&, const QPromiseReject<int>& reject) {
|
|
|
|
|
qtpromise_defer([=]() {
|
|
|
|
|
reject(8);
|
|
|
|
|
});
|
2017-05-15 01:03:01 +08:00
|
|
|
|
});
|
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
qPromiseAll(QVector<QPromise<int> >{
|
|
|
|
|
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();
|
|
|
|
|
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QCOMPARE(values, QVector<int>({9, 10, 11}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tst_requirements::thenHandlers()
|
|
|
|
|
{
|
|
|
|
|
// 2.2.7. then must return a promise: p2 = p1.then(onFulfilled, onRejected);
|
|
|
|
|
{
|
|
|
|
|
auto handler = [](){ return 42; };
|
2017-05-20 15:40:42 +08:00
|
|
|
|
auto p1 = QPromise<int>::resolve(42);
|
2017-05-15 01:03:01 +08:00
|
|
|
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, nullptr)), QPromise<int> >::value));
|
|
|
|
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(nullptr, handler)), QPromise<int> >::value));
|
|
|
|
|
Q_STATIC_ASSERT((std::is_same<decltype(p1.then(handler, handler)), QPromise<int> >::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;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
auto p1 = QPromise<int>::resolve(42);
|
|
|
|
|
auto p2 = p1.then([](){ throw QString("foo"); });
|
|
|
|
|
p2.then(nullptr, [&](const QString& e) { reason = e; }).wait();
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(p1.isFulfilled());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p2.isRejected());
|
|
|
|
|
QCOMPARE(reason, QString("foo"));
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
QString reason;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
auto p1 = QPromise<int>::reject(QString("foo"));
|
|
|
|
|
auto p2 = p1.then(nullptr, [](){ throw QString("bar"); return 42; });
|
|
|
|
|
p2.then(nullptr, [&](const QString& e) { reason = e; return 0; }).wait();
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(p1.isRejected());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
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;
|
2017-05-20 15:40:42 +08:00
|
|
|
|
auto p1 = QPromise<QString>::resolve("42");
|
|
|
|
|
auto p2 = p1.then(nullptr, [](){ return QString(); });
|
2017-05-15 01:03:01 +08:00
|
|
|
|
Q_STATIC_ASSERT((std::is_same<decltype(p2), QPromise<QString> >::value));
|
2017-05-20 15:40:42 +08:00
|
|
|
|
p2.then([&](const QString& e) { value = e; }).wait();
|
2017-05-15 01:03:01 +08:00
|
|
|
|
|
2017-05-20 15:40:42 +08:00
|
|
|
|
QVERIFY(p1.isFulfilled());
|
2017-05-15 01:03:01 +08:00
|
|
|
|
QVERIFY(p2.isFulfilled());
|
|
|
|
|
QCOMPARE(value, QString("42"));
|
|
|
|
|
}
|
|
|
|
|
}
|