| | #include "catch2/catch.hpp"
|
| | #include <fastsignals/signal.h>
|
| | #include <string>
|
| |
|
| | using namespace fastsignals;
|
| | using namespace std::literals;
|
| |
|
| | namespace
|
| | {
|
| | template <class T>
|
| | class any_of_combiner
|
| | {
|
| | public:
|
| | static_assert(std::is_same_v<T, bool>);
|
| |
|
| | using result_type = bool;
|
| |
|
| | template <class TRef>
|
| | void operator()(TRef&& value)
|
| | {
|
| | m_result = m_result || bool(value);
|
| | }
|
| |
|
| | result_type get_value() const
|
| | {
|
| | return m_result;
|
| | }
|
| |
|
| | private:
|
| | result_type m_result = {};
|
| | };
|
| | }
|
| |
|
| | TEST_CASE("Can connect a few slots and emit", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | });
|
| | valueChanged.connect([&value2](int value) {
|
| | value2 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| |
|
| | valueChanged(10);
|
| | REQUIRE(value1 == 10);
|
| | REQUIRE(value2 == 10);
|
| | }
|
| |
|
| | TEST_CASE("Can safely pass rvalues", "[signal]")
|
| | {
|
| | const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T.";
|
| | std::string passedValue = expected;
|
| | signal<void(std::string)> valueChanged;
|
| |
|
| | std::string value1;
|
| | std::string value2;
|
| | valueChanged.connect([&value1](std::string value) {
|
| | value1 = value;
|
| | });
|
| | valueChanged.connect([&value2](std::string value) {
|
| | value2 = value;
|
| | });
|
| |
|
| | valueChanged(std::move(passedValue));
|
| | REQUIRE(value1 == expected);
|
| | REQUIRE(value2 == expected);
|
| | }
|
| |
|
| | TEST_CASE("Can pass mutable ref", "[signal]")
|
| | {
|
| | const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T.";
|
| | signal<void(std::string&)> valueChanged;
|
| |
|
| | std::string passedValue;
|
| | valueChanged.connect([expected](std::string& value) {
|
| | value = expected;
|
| | });
|
| | valueChanged(passedValue);
|
| |
|
| | REQUIRE(passedValue == expected);
|
| | }
|
| |
|
| | TEST_CASE("Can disconnect slot with explicit call", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | auto conn1 = valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | });
|
| | auto conn2 = valueChanged.connect([&value2](int value) {
|
| | value2 = value;
|
| | });
|
| | valueChanged.connect([&value3](int value) {
|
| | value3 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| | REQUIRE(value3 == 0);
|
| |
|
| | valueChanged(10);
|
| | REQUIRE(value1 == 10);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 10);
|
| |
|
| | conn2.disconnect();
|
| | valueChanged(-99);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == -99);
|
| |
|
| | conn1.disconnect();
|
| | valueChanged(17);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 17);
|
| | }
|
| |
|
| | TEST_CASE("Can disconnect slot with scoped_connection", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | {
|
| | scoped_connection conn1 = valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | });
|
| | {
|
| | scoped_connection conn2 = valueChanged.connect([&value2](int value) {
|
| | value2 = value;
|
| | });
|
| | valueChanged.connect([&value3](int value) {
|
| | value3 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| | REQUIRE(value3 == 0);
|
| |
|
| | valueChanged(10);
|
| | REQUIRE(value1 == 10);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 10);
|
| | }
|
| |
|
| |
|
| | valueChanged(-99);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == -99);
|
| | }
|
| |
|
| |
|
| | valueChanged(17);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 17);
|
| | }
|
| |
|
| | TEST_CASE("Can disconnect all", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | });
|
| | valueChanged.connect([&value2](int value) {
|
| | value2 = value;
|
| | });
|
| | valueChanged.connect([&value3](int value) {
|
| | value3 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| | REQUIRE(value3 == 0);
|
| |
|
| | valueChanged(63);
|
| | REQUIRE(value1 == 63);
|
| | REQUIRE(value2 == 63);
|
| | REQUIRE(value3 == 63);
|
| |
|
| | valueChanged.disconnect_all_slots();
|
| | valueChanged(101);
|
| | REQUIRE(value1 == 63);
|
| | REQUIRE(value2 == 63);
|
| | REQUIRE(value3 == 63);
|
| | }
|
| |
|
| | TEST_CASE("Can disconnect inside slot", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | connection conn2;
|
| | valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | });
|
| | conn2 = valueChanged.connect([&](int value) {
|
| | value2 = value;
|
| | conn2.disconnect();
|
| | });
|
| | valueChanged.connect([&value3](int value) {
|
| | value3 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| | REQUIRE(value3 == 0);
|
| |
|
| | valueChanged(63);
|
| | REQUIRE(value1 == 63);
|
| | REQUIRE(value2 == 63);
|
| | REQUIRE(value3 == 63);
|
| |
|
| | valueChanged(101);
|
| | REQUIRE(value1 == 101);
|
| | REQUIRE(value2 == 63);
|
| | REQUIRE(value3 == 101);
|
| | }
|
| |
|
| | TEST_CASE("Disconnects OK if signal dead first", "[signal]")
|
| | {
|
| | connection conn2;
|
| | {
|
| | scoped_connection conn1;
|
| | {
|
| | signal<void(int)> valueChanged;
|
| | conn2 = valueChanged.connect([](int) {
|
| | });
|
| |
|
| | valueChanged.connect([](int) {
|
| | });
|
| | conn1 = valueChanged.connect([](int) {
|
| | });
|
| | }
|
| | REQUIRE(conn2.connected());
|
| | REQUIRE(conn1.connected());
|
| | conn2.disconnect();
|
| | REQUIRE(!conn2.connected());
|
| | REQUIRE(conn1.connected());
|
| | }
|
| | conn2.disconnect();
|
| | }
|
| |
|
| | TEST_CASE("Returns last called slot result with default combiner", "[signal]")
|
| | {
|
| | connection conn2;
|
| | {
|
| | scoped_connection conn1;
|
| | {
|
| | signal<int(int)> absSignal;
|
| | conn2 = absSignal.connect([](int value) {
|
| | return value * value;
|
| | });
|
| | conn1 = absSignal.connect([](int value) {
|
| | return abs(value);
|
| | });
|
| | absSignal(-1);
|
| |
|
| | REQUIRE(absSignal(45) == 45);
|
| | REQUIRE(absSignal(-1) == 1);
|
| | REQUIRE(absSignal(-177) == 177);
|
| | REQUIRE(absSignal(0) == 0);
|
| | }
|
| | REQUIRE(conn2.connected());
|
| | conn2.disconnect();
|
| | REQUIRE(!conn2.connected());
|
| | }
|
| | conn2.disconnect();
|
| | REQUIRE(!conn2.connected());
|
| | }
|
| |
|
| | TEST_CASE("Works with custom any_of combiner", "[signal]")
|
| | {
|
| | using cancellable_signal = signal<bool(std::string), any_of_combiner>;
|
| | cancellable_signal startRequested;
|
| | auto conn1 = startRequested.connect([](std::string op) {
|
| | return op == "1";
|
| | });
|
| | auto conn2 = startRequested.connect([](std::string op) {
|
| | return op == "1" || op == "2";
|
| | });
|
| | REQUIRE(startRequested("0") == false);
|
| | REQUIRE(startRequested("1") == true);
|
| | REQUIRE(startRequested("2") == true);
|
| | REQUIRE(startRequested("3") == false);
|
| | conn1.disconnect();
|
| | conn2.disconnect();
|
| | REQUIRE(startRequested("0") == false);
|
| | REQUIRE(startRequested("1") == false);
|
| | REQUIRE(startRequested("2") == false);
|
| | REQUIRE(startRequested("3") == false);
|
| | }
|
| |
|
| | TEST_CASE("Can release scoped connection", "[signal]")
|
| | {
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | signal<void(int)> valueChanged;
|
| | connection conn1;
|
| | {
|
| | scoped_connection conn2;
|
| | scoped_connection conn3;
|
| | conn2 = valueChanged.connect([&value2](int x) {
|
| | value2 = x;
|
| | });
|
| | conn3 = valueChanged.connect([&value3](int x) {
|
| | value3 = x;
|
| | });
|
| |
|
| | valueChanged(42);
|
| | REQUIRE(value2 == 42);
|
| | REQUIRE(value3 == 42);
|
| | REQUIRE(conn2.connected());
|
| | REQUIRE(conn3.connected());
|
| | REQUIRE(!conn1.connected());
|
| |
|
| | conn1 = conn3.release();
|
| | REQUIRE(conn2.connected());
|
| | REQUIRE(!conn3.connected());
|
| | REQUIRE(conn1.connected());
|
| | valueChanged(144);
|
| | REQUIRE(value2 == 144);
|
| | REQUIRE(value3 == 144);
|
| | }
|
| |
|
| |
|
| | valueChanged(17);
|
| | REQUIRE(value2 == 144);
|
| | REQUIRE(value3 == 17);
|
| | REQUIRE(conn1.connected());
|
| |
|
| | conn1.disconnect();
|
| | valueChanged(90);
|
| | REQUIRE(value2 == 144);
|
| | REQUIRE(value3 == 17);
|
| | }
|
| |
|
| | TEST_CASE("Can use signal with more than one argument", "[signal]")
|
| | {
|
| | signal<void(int, std::string, std::vector<std::string>)> event;
|
| |
|
| | int value1 = 0;
|
| | std::string value2;
|
| | std::vector<std::string> value3;
|
| | event.connect([&](int v1, const std::string& v2, const std::vector<std::string>& v3) {
|
| | value1 = v1;
|
| | value2 = v2;
|
| | value3 = v3;
|
| | });
|
| |
|
| | event(9815, "using namespace std::literals!"s, std::vector{ "std::vector"s, "using namespace std::literals"s });
|
| | REQUIRE(value1 == 9815);
|
| | REQUIRE(value2 == "using namespace std::literals!"s);
|
| | REQUIRE(value3 == std::vector{ "std::vector"s, "using namespace std::literals"s });
|
| | }
|
| |
|
| | TEST_CASE("Can blocks slots using shared_connection_block", "[signal]")
|
| | {
|
| | bool callbackShouldBeCalled = true;
|
| | bool callbackCalled = false;
|
| | const int value = 123;
|
| | signal<void(int)> event;
|
| | auto conn = event.connect([&](int gotValue) {
|
| | CHECK(gotValue == value);
|
| | callbackCalled = true;
|
| | if (!callbackShouldBeCalled)
|
| | {
|
| | FAIL("callback is blocked and should not be called");
|
| | }
|
| | },
|
| | advanced_tag{});
|
| | event(value);
|
| | REQUIRE(callbackCalled);
|
| | shared_connection_block block(conn);
|
| | callbackShouldBeCalled = false;
|
| | callbackCalled = false;
|
| | event(value);
|
| | REQUIRE(!callbackCalled);
|
| | block.unblock();
|
| | callbackShouldBeCalled = true;
|
| | event(value);
|
| | REQUIRE(callbackCalled);
|
| | }
|
| |
|
| | TEST_CASE("Other slots are unaffected by the block", "[signal]")
|
| | {
|
| | bool callback1Called = false;
|
| | bool callback2Called = false;
|
| | const int value = 123;
|
| | signal<void(int)> event;
|
| | auto conn1 = event.connect([&](int gotValue) {
|
| | CHECK(gotValue == value);
|
| | callback1Called = true;
|
| | },
|
| | advanced_tag{});
|
| | auto conn2 = event.connect([&](int) {
|
| | callback2Called = true;
|
| | FAIL("callback is blocked and should not be called");
|
| | },
|
| | advanced_tag{});
|
| | shared_connection_block block(conn2);
|
| | event(value);
|
| | REQUIRE(callback1Called);
|
| | REQUIRE(!callback2Called);
|
| | }
|
| |
|
| | TEST_CASE("Multiple blocks block until last one is unblocked", "[signal]")
|
| | {
|
| | bool callbackShouldBeCalled = false;
|
| | bool callbackCalled = false;
|
| | const int value = 123;
|
| | signal<void(int)> event;
|
| | auto conn = event.connect([&](int gotValue) {
|
| | CHECK(gotValue == value);
|
| | callbackCalled = true;
|
| | if (!callbackShouldBeCalled)
|
| | {
|
| | FAIL("callback is blocked and should not be called");
|
| | }
|
| | },
|
| | advanced_tag{});
|
| | shared_connection_block block1(conn);
|
| | shared_connection_block block2(conn);
|
| | event(value);
|
| | REQUIRE(!callbackCalled);
|
| | block1.unblock();
|
| | event(value);
|
| | REQUIRE(!callbackCalled);
|
| | block1.block();
|
| | block2.unblock();
|
| | event(value);
|
| | REQUIRE(!callbackCalled);
|
| | block1.unblock();
|
| | callbackShouldBeCalled = true;
|
| | event(value);
|
| | REQUIRE(callbackCalled);
|
| | }
|
| |
|
| | TEST_CASE("Can copy and move shared_connection_block objects", "[signal]")
|
| | {
|
| | bool callbackShouldBeCalled = false;
|
| | bool callbackCalled = false;
|
| | const int value = 123;
|
| | signal<void(int)> event;
|
| | auto conn = event.connect([&](int gotValue) {
|
| | CHECK(gotValue == value);
|
| | callbackCalled = true;
|
| | if (!callbackShouldBeCalled)
|
| | {
|
| | FAIL("callback is blocked and should not be called");
|
| | }
|
| | },
|
| | advanced_tag{});
|
| | shared_connection_block block1(conn);
|
| |
|
| | shared_connection_block block2(block1);
|
| | event(value);
|
| | REQUIRE(block1.blocking());
|
| | REQUIRE(block2.blocking());
|
| | REQUIRE(!callbackCalled);
|
| |
|
| | shared_connection_block block3(std::move(block2));
|
| | event(value);
|
| | REQUIRE(block1.blocking());
|
| | REQUIRE(!block2.blocking());
|
| | REQUIRE(block3.blocking());
|
| | REQUIRE(!callbackCalled);
|
| |
|
| | block2 = block3;
|
| | event(value);
|
| | REQUIRE(block1.blocking());
|
| | REQUIRE(block2.blocking());
|
| | REQUIRE(block3.blocking());
|
| | REQUIRE(!callbackCalled);
|
| |
|
| | block3 = std::move(block2);
|
| | event(value);
|
| | REQUIRE(block1.blocking());
|
| | REQUIRE(!block2.blocking());
|
| | REQUIRE(block3.blocking());
|
| | REQUIRE(!callbackCalled);
|
| |
|
| | block3 = shared_connection_block(conn, false);
|
| | event(value);
|
| | REQUIRE(block1.blocking());
|
| | REQUIRE(!block2.blocking());
|
| | REQUIRE(!block3.blocking());
|
| | REQUIRE(!callbackCalled);
|
| |
|
| | block1.unblock();
|
| | callbackShouldBeCalled = true;
|
| | event(value);
|
| | REQUIRE(!block1.blocking());
|
| | REQUIRE(!block2.blocking());
|
| | REQUIRE(!block3.blocking());
|
| | REQUIRE(callbackCalled);
|
| | }
|
| |
|
| | TEST_CASE("Unblocks when shared_connection_block goes out of scope")
|
| | {
|
| | bool callbackCalled = false;
|
| | const int value = 123;
|
| | signal<void(int)> event;
|
| | auto conn = event.connect([&](int gotValue) {
|
| | CHECK(gotValue == value);
|
| | callbackCalled = true;
|
| | },
|
| | advanced_tag{});
|
| |
|
| | callbackCalled = false;
|
| | event(value);
|
| | CHECK(callbackCalled);
|
| |
|
| | {
|
| | callbackCalled = false;
|
| | shared_connection_block block(conn);
|
| | event(value);
|
| | CHECK(!callbackCalled);
|
| |
|
| | {
|
| | callbackCalled = false;
|
| | shared_connection_block block2(conn);
|
| | event(value);
|
| | CHECK(!callbackCalled);
|
| | }
|
| | }
|
| |
|
| | callbackCalled = false;
|
| | event(value);
|
| | CHECK(callbackCalled);
|
| | }
|
| |
|
| | TEST_CASE("Can disconnect advanced slot using advanced_scoped_connection", "[signal]")
|
| | {
|
| | signal<void(int)> valueChanged;
|
| |
|
| | int value1 = 0;
|
| | int value2 = 0;
|
| | int value3 = 0;
|
| | {
|
| | advanced_scoped_connection conn1 = valueChanged.connect([&value1](int value) {
|
| | value1 = value;
|
| | },
|
| | advanced_tag{});
|
| | {
|
| | advanced_scoped_connection conn2 = valueChanged.connect([&value2](int value) {
|
| | value2 = value;
|
| | },
|
| | advanced_tag{});
|
| | valueChanged.connect([&value3](int value) {
|
| | value3 = value;
|
| | });
|
| | REQUIRE(value1 == 0);
|
| | REQUIRE(value2 == 0);
|
| | REQUIRE(value3 == 0);
|
| |
|
| | valueChanged(10);
|
| | REQUIRE(value1 == 10);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 10);
|
| | }
|
| |
|
| |
|
| | valueChanged(-99);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == -99);
|
| | }
|
| |
|
| |
|
| | valueChanged(17);
|
| | REQUIRE(value1 == -99);
|
| | REQUIRE(value2 == 10);
|
| | REQUIRE(value3 == 17);
|
| | }
|
| |
|
| | TEST_CASE("Can move signal", "[signal]")
|
| | {
|
| | signal<void()> src;
|
| |
|
| | int srcFireCount = 0;
|
| | auto srcConn = src.connect([&srcFireCount] {
|
| | ++srcFireCount;
|
| | });
|
| |
|
| | src();
|
| | REQUIRE(srcFireCount == 1);
|
| |
|
| | auto dst = std::move(src);
|
| |
|
| | int dstFireCount = 0;
|
| | auto dstConn = dst.connect([&dstFireCount] {
|
| | ++dstFireCount;
|
| | });
|
| |
|
| | dst();
|
| | REQUIRE(srcFireCount == 2);
|
| | REQUIRE(dstFireCount == 1);
|
| |
|
| | srcConn.disconnect();
|
| | dstConn.disconnect();
|
| | dst();
|
| | REQUIRE(srcFireCount == 2);
|
| | REQUIRE(dstFireCount == 1);
|
| | }
|
| |
|
| | TEST_CASE("Can swap signals", "[signal]")
|
| | {
|
| | signal<void()> s1;
|
| | signal<void()> s2;
|
| |
|
| | int s1FireCount = 0;
|
| | int s2FireCount = 0;
|
| |
|
| | s1.connect([&s1FireCount] {
|
| | ++s1FireCount;
|
| | });
|
| |
|
| | s2.connect([&s2FireCount] {
|
| | ++s2FireCount;
|
| | });
|
| |
|
| | std::swap(s1, s2);
|
| |
|
| | s1();
|
| | REQUIRE(s1FireCount == 0);
|
| | REQUIRE(s2FireCount == 1);
|
| |
|
| | s2();
|
| | REQUIRE(s1FireCount == 1);
|
| | REQUIRE(s2FireCount == 1);
|
| | }
|
| |
|
| | TEST_CASE("Signal can be destroyed inside its slot and will call the rest of its slots", "[signal]")
|
| | {
|
| | std::optional<signal<void()>> s;
|
| | s.emplace();
|
| | s->connect([&] {
|
| | s.reset();
|
| | });
|
| | bool called = false;
|
| | s->connect([&] {
|
| | called = true;
|
| | });
|
| | (*s)();
|
| | CHECK(called);
|
| | }
|
| |
|
| | TEST_CASE("Signal can be used as a slot for another signal", "[signal]")
|
| | {
|
| | signal<void()> s1;
|
| | bool called = false;
|
| | s1.connect([&] {
|
| | called = true;
|
| | });
|
| |
|
| | signal<void()> s2;
|
| | s2.connect(s1);
|
| |
|
| | s2();
|
| |
|
| | CHECK(called);
|
| | }
|
| |
|
| |
|
| | TEST_CASE("Releases lambda and its captured const data", "[signal]")
|
| | {
|
| | struct Captured
|
| | {
|
| | Captured(bool& released)
|
| | : m_released(released)
|
| | {
|
| | }
|
| |
|
| | ~Captured()
|
| | {
|
| | m_released = true;
|
| | }
|
| |
|
| | private:
|
| | bool& m_released;
|
| | };
|
| |
|
| | bool released = false;
|
| |
|
| | {
|
| | const auto captured = std::make_shared<Captured>(released);
|
| |
|
| | signal<void()> changeSignal;
|
| | changeSignal.connect([captured]{});
|
| | }
|
| |
|
| | CHECK(released);
|
| | } |