// QtPromise #include // Qt #include using namespace QtPromise; using namespace QtPromisePrivate; // 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: // 2.1.1.1. may transition to either the fulfilled state { QPromise p([&](const QPromiseResolve& resolve) { qtpromise_defer([=]() { resolve(42); }); }); QVERIFY(p.isPending()); QVERIFY(!p.isFulfilled()); QVERIFY(!p.isRejected()); p.wait(); QVERIFY(!p.isPending()); QVERIFY(p.isFulfilled()); QVERIFY(!p.isRejected()); } // 2.1.1.1. ... or the rejected state { QPromise p([&](const QPromiseResolve&, const QPromiseReject& reject) { qtpromise_defer([=]() { reject(QString("foo")); }); }); QVERIFY(p.isPending()); QVERIFY(!p.isFulfilled()); QVERIFY(!p.isRejected()); p.wait(); QVERIFY(!p.isPending()); QVERIFY(!p.isFulfilled()); QVERIFY(p.isRejected()); } } void tst_requirements::stateFulfilled() { QString error; int value = -1; // 2.1.2. When fulfilled, a promise: QPromise p([]( const QPromiseResolve& resolve, const QPromiseReject& 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")); }); }); QVERIFY(p.isPending()); p.then([&](int res) { value = res; }, [&](const QString& err) { error = err; }).wait(); QVERIFY(p.isFulfilled()); QVERIFY(!p.isRejected()); QVERIFY(error.isEmpty()); QCOMPARE(value, 42); } void tst_requirements::stateRejected() { QString error; int value = -1; // 2.1.3 When rejected, a promise: QPromise p([]( const QPromiseResolve& resolve, const QPromiseReject& 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); }); }); QVERIFY(p.isPending()); p.then([&](int res) { value = res; }, [&](const QString& err) { error = err; }).wait(); QVERIFY(!p.isFulfilled()); QVERIFY(p.isRejected()); QCOMPARE(error, QString("foo")); QCOMPARE(value, -1); } void tst_requirements::thenArguments() { // 2.2.1. Both onFulfilled and onRejected are given { QString error; int value = -1; QPromise::resolve(42).then( [&](int res) { value = res; }, [&](const QString& err) { error = err; } ).wait(); QVERIFY(error.isEmpty()); QCOMPARE(value, 42); } { QString error; int value = -1; QPromise::reject(QString("foo")).then( [&](int res) { value = res; }, [&](const QString& err){ error = err; } ).wait(); QCOMPARE(error, QString("foo")); QCOMPARE(value, -1); } // 2.2.1. onFulfilled is an optional arguments: { QString error; QPromise::reject(QString("foo")).then( nullptr, [&](const QString& err){ error = err; return 42; } ).wait(); QCOMPARE(error, QString("foo")); } // 2.2.1. onRejected is an optional arguments: { int value = -1; QPromise::resolve(42).then( [&value](int res) { value = res; } ).wait(); 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 values; QPromise p0([](const QPromiseResolve& 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; }); // 2.2.2.2. it must not be called before promise is fulfilled. QVERIFY(p0.isPending()); QVERIFY(p1.isPending()); QVERIFY(values.isEmpty()); p1.wait(); // 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({42})); } void tst_requirements::thenOnRejected() { // 2.2.3. If onRejected is a function: QStringList errors; QPromise p0([](const QPromiseResolve&, const QPromiseReject& 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; }); // 2.2.3.2. it must not be called before promise is rejected. QVERIFY(p0.isPending()); QVERIFY(p1.isPending()); QVERIFY(errors.isEmpty()); p1.wait(); // 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). int value = -1; auto p0 = QPromise::resolve(42); QVERIFY(p0.isFulfilled()); auto p1 = p0.then([&](int res){ value = res; }); QVERIFY(p1.isPending()); QCOMPARE(value, -1); 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 values; QPromise p([](const QPromiseResolve& resolve) { qtpromise_defer([=]() { resolve(42); }); }); qPromiseAll(QVector >{ p.then([&](int r) { values << r + 1; }), p.then([&](int r) { values << r + 2; }), p.then([&](int r) { values << r + 3; }) }).wait(); QCOMPARE(values, QVector({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 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")); } }