<commit_before>//
//  yas_ui_node_tests.mm
//

#import <XCTest/XCTest.h>
#import <iostream>
#import "yas_objc_ptr.h"
#import "yas_ui.h"
#import "yas_ui_render_info.h"
#import "yas_ui_math.h"

using namespace yas;

namespace yas {
namespace test {
    struct test_render_encoder : base {
        struct impl : base::impl, ui::render_encodable::impl {
            void push_back_mesh(ui::mesh &&mesh) {
                _meshes.emplace_back(std::move(mesh));
            }

            std::vector<ui::mesh> _meshes;
        };

        test_render_encoder() : base(std::make_shared<impl>()) {
        }

        ui::render_encodable encodable() {
            return ui::render_encodable{impl_ptr<ui::render_encodable::impl>()};
        }

        std::vector<ui::mesh> &meshes() {
            return impl_ptr<impl>()->_meshes;
        }
    };
}
}

@interface yas_ui_node_tests : XCTestCase

@end

@implementation yas_ui_node_tests

- (void)setUp {
    [super setUp];
}

- (void)tearDown {
    [super tearDown];
}

- (void)test_create {
    ui::node node;

    XCTAssertEqual(node.position().x, 0.0f);
    XCTAssertEqual(node.position().y, 0.0f);
    XCTAssertEqual(node.angle(), 0.0f);
    XCTAssertEqual(node.radians(), 0.0f);
    XCTAssertEqual(node.scale().width, 1.0f);
    XCTAssertEqual(node.scale().height, 1.0f);

    XCTAssertEqual(node.color().red, 1.0f);
    XCTAssertEqual(node.color().green, 1.0f);
    XCTAssertEqual(node.color().blue, 1.0f);
    XCTAssertEqual(node.alpha(), 1.0f);

    XCTAssertFalse(node.mesh());
    XCTAssertFalse(node.collider());

    XCTAssertEqual(node.children().size(), 0);
    XCTAssertFalse(node.parent());
    XCTAssertFalse(node.renderer());

    XCTAssertTrue(node.is_enabled());

    XCTAssertTrue(node.renderable());
    XCTAssertTrue(node.metal());
}

- (void)test_create_null {
    ui::node node{nullptr};

    XCTAssertFalse(node);
}

- (void)test_set_variables {
    ui::node node;
    ui::mesh mesh;
    ui::collider collider;
    ui::batch batch;

    node.set_position({1.0f, 2.0f});
    node.set_angle(3.0f);
    node.set_scale({4.0f, 5.0f});
    node.set_color({0.1f, 0.2f, 0.3f});
    node.set_alpha(0.4f);

    node.set_enabled(true);

    XCTAssertEqual(node.position().x, 1.0f);
    XCTAssertEqual(node.position().y, 2.0f);
    XCTAssertEqual(node.angle(), 3.0f);
    XCTAssertEqualWithAccuracy(node.radians(), radians_from_degrees(3.0f), 0.001f);
    XCTAssertEqual(node.scale().width, 4.0f);
    XCTAssertEqual(node.scale().height, 5.0f);
    XCTAssertEqual(node.color().red, 0.1f);
    XCTAssertEqual(node.color().green, 0.2f);
    XCTAssertEqual(node.color().blue, 0.3f);
    XCTAssertEqual(node.alpha(), 0.4f);

    node.set_mesh(mesh);
    XCTAssertTrue(node.mesh());
    XCTAssertEqual(node.mesh(), mesh);

    node.set_collider(collider);
    XCTAssertTrue(node.collider());
    XCTAssertEqual(node.collider(), collider);

    node.set_batch(batch);
    XCTAssertTrue(node.batch());
    XCTAssertEqual(node.batch(), batch);

    node.set_batch(nullptr);
    XCTAssertFalse(node.batch());

    XCTAssertTrue(node.is_enabled());
}

- (void)test_const_variables {
    ui::node node;
    ui::node const &const_node = node;

    XCTAssertFalse(const_node.mesh());
    XCTAssertFalse(const_node.collider());
    XCTAssertFalse(const_node.batch());

    node.set_mesh(ui::mesh{});
    node.set_collider(ui::collider{});
    node.set_batch(ui::batch{});

    XCTAssertTrue(const_node.mesh());
    XCTAssertTrue(const_node.collider());
    XCTAssertEqual(const_node.children().size(), 0);
    XCTAssertTrue(const_node.batch());
}

- (void)set_color_to_mesh {
    ui::node node;
    ui::mesh mesh;

    XCTAssertEqual(mesh.color()[0], 1.0f);
    XCTAssertEqual(mesh.color()[1], 1.0f);
    XCTAssertEqual(mesh.color()[2], 1.0f);
    XCTAssertEqual(mesh.color()[3], 1.0f);

    node.set_color({0.25f, 0.5f, 0.75f});
    node.set_alpha(0.125f);

    node.set_mesh(mesh);

    XCTAssertEqual(mesh.color()[0], 0.25f);
    XCTAssertEqual(mesh.color()[1], 0.5f);
    XCTAssertEqual(mesh.color()[2], 0.75f);
    XCTAssertEqual(mesh.color()[3], 0.125f);

    node.set_color({0.1f, 0.2f, 0.3f});
    node.set_alpha(0.4f);

    XCTAssertEqual(mesh.color()[0], 0.1f);
    XCTAssertEqual(mesh.color()[1], 0.2f);
    XCTAssertEqual(mesh.color()[2], 0.3f);
    XCTAssertEqual(mesh.color()[3], 0.4f);
}

- (void)test_is_equal {
    ui::node node1;
    ui::node node1b = node1;
    ui::node node2;

    XCTAssertTrue(node1 == node1);
    XCTAssertTrue(node1 == node1b);
    XCTAssertFalse(node1 == node2);

    XCTAssertFalse(node1 != node1);
    XCTAssertFalse(node1 != node1b);
    XCTAssertTrue(node1 != node2);
}

- (void)test_hierarchie {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;

    XCTAssertEqual(parent_node.children().size(), 0);
    XCTAssertFalse(sub_node1.parent());
    XCTAssertFalse(sub_node2.parent());

    parent_node.push_back_sub_node(sub_node1);

    XCTAssertEqual(parent_node.children().size(), 1);
    XCTAssertTrue(sub_node1.parent());

    parent_node.push_back_sub_node(sub_node2);

    XCTAssertEqual(parent_node.children().size(), 2);
    XCTAssertTrue(sub_node1.parent());
    XCTAssertTrue(sub_node2.parent());

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
    XCTAssertEqual(sub_node1.parent(), parent_node);
    XCTAssertEqual(sub_node2.parent(), parent_node);

    sub_node1.remove_from_super_node();

    XCTAssertEqual(parent_node.children().size(), 1);
    XCTAssertFalse(sub_node1.parent());
    XCTAssertTrue(sub_node2.parent());

    XCTAssertEqual(parent_node.children().at(0), sub_node2);

    sub_node2.remove_from_super_node();

    XCTAssertEqual(parent_node.children().size(), 0);
    XCTAssertFalse(sub_node2.parent());
}

- (void)test_push_front_sub_node {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;

    parent_node.push_back_sub_node(sub_node2);
    parent_node.push_front_sub_node(sub_node1);

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
}

- (void)test_insert_sub_node {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;
    ui::node sub_node3;

    parent_node.push_back_sub_node(sub_node1);
    parent_node.push_back_sub_node(sub_node3);
    parent_node.insert_sub_node(sub_node2, 1);

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
    XCTAssertEqual(parent_node.children().at(2), sub_node3);
}

- (void)test_renderable_node {
    ui::node node;
    ui::renderer renderer;

    auto &renderable = node.renderable();

    XCTAssertFalse(renderable.renderer());

    renderable.set_renderer(renderer);

    XCTAssertTrue(renderable.renderer());
    XCTAssertEqual(renderable.renderer(), renderer);
}

- (void)test_add_and_remove_node_observed {
    ui::node parent_node;
    ui::node sub_node;
    int observer_called_count = 0;
    bool added_to_super_called = false;
    bool removed_from_super_called = false;

    auto observer = sub_node.subject().make_wild_card_observer(
        [&observer_called_count, &added_to_super_called, &removed_from_super_called](auto const &context) {
            switch (context.key) {
                case ui::node::method::added_to_super:
                    added_to_super_called = true;
                    break;
                case ui::node::method::removed_from_super:
                    removed_from_super_called = true;
                    break;

                default:
                    break;
            }
            ++observer_called_count;
        });

    parent_node.push_back_sub_node(sub_node);

    XCTAssertTrue(added_to_super_called);

    sub_node.remove_from_super_node();

    XCTAssertTrue(removed_from_super_called);

    XCTAssertEqual(observer_called_count, 2);
}

- (void)test_method_undispatched {
    ui::node node;
    ui::renderer renderer;

    std::shared_ptr<ui::node::method> called_method = nullptr;

    auto observer = node.subject().make_wild_card_observer([&called_method](auto const &context) mutable {
        auto const &method = context.key;
        if (method != ui::node::method::added_to_super && method != ui::node::method::removed_from_super) {
            called_method = std::make_shared<ui::node::method>(context.key);
        }
    });

    node.set_position({1.0f, 2.0f});
    XCTAssertFalse(called_method);
    node.set_angle(90.0f);
    XCTAssertFalse(called_method);
    node.set_scale({3.0f, 4.0f});
    XCTAssertFalse(called_method);
    node.set_color({1.0f, 2.0f, 3.0f});
    XCTAssertFalse(called_method);
    node.set_alpha(5.0f);
    XCTAssertFalse(called_method);
    node.set_enabled(false);
    XCTAssertFalse(called_method);
    node.set_mesh(ui::mesh{});
    XCTAssertFalse(called_method);
    node.set_collider(ui::collider{});
    XCTAssertFalse(called_method);

    ui::node parent;
    parent.push_back_sub_node(node);
    XCTAssertFalse(called_method);

    node.renderable().set_renderer(renderer);
    XCTAssertFalse(called_method);
}

- (void)test_method_dispatched {
    std::shared_ptr<ui::node::method> called_method = nullptr;

    auto make_observer = [&called_method](ui::node &node) {
        return node.subject().make_wild_card_observer([&called_method](auto const &context) mutable {
            auto const &method = context.key;
            if (method != ui::node::method::added_to_super && method != ui::node::method::removed_from_super) {
                called_method = std::make_shared<ui::node::method>(context.key);
            }
        });
    };

    {
        ui::node node;
        node.dispatch_method(ui::node::method::position_changed);
        auto observer = make_observer(node);
        node.set_position({1.0f, 2.0f});
        XCTAssertEqual(*called_method, ui::node::method::position_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::angle_changed);
        auto observer = make_observer(node);
        node.set_angle(90.0f);
        XCTAssertEqual(*called_method, ui::node::method::angle_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::scale_changed);
        auto observer = make_observer(node);
        node.set_scale({3.0f, 4.0f});
        XCTAssertEqual(*called_method, ui::node::method::scale_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::color_changed);
        auto observer = make_observer(node);
        node.set_color({1.0f, 2.0f, 3.0f});
        XCTAssertEqual(*called_method, ui::node::method::color_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::alpha_changed);
        auto observer = make_observer(node);
        node.set_alpha(0.5f);
        XCTAssertEqual(*called_method, ui::node::method::alpha_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::enabled_changed);
        auto observer = make_observer(node);
        node.set_enabled(false);
        XCTAssertEqual(*called_method, ui::node::method::enabled_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::mesh_changed);
        auto observer = make_observer(node);
        node.set_mesh(ui::mesh{});
        XCTAssertEqual(*called_method, ui::node::method::mesh_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::collider_changed);
        auto observer = make_observer(node);
        node.set_collider(ui::collider{});
        XCTAssertEqual(*called_method, ui::node::method::collider_changed);
    }

    called_method = nullptr;

    {
        ui::node parent;
        ui::node node;
        node.dispatch_method(ui::node::method::parent_changed);
        auto observer = make_observer(node);
        parent.push_back_sub_node(node);
        XCTAssertEqual(*called_method, ui::node::method::parent_changed);
    }

    called_method = nullptr;

    {
        ui::renderer renderer;
        ui::node node;
        node.dispatch_method(ui::node::method::renderer_changed);
        auto observer = make_observer(node);
        node.renderable().set_renderer(renderer);
        XCTAssertEqual(*called_method, ui::node::method::renderer_changed);
    }

    called_method = nullptr;
}

- (void)test_fetch_updates {
    ui::node node;

    ui::mesh mesh;
    node.set_mesh(mesh);

    ui::dynamic_mesh_data mesh_data{{.vertex_count = 2, .index_count = 2}};
    mesh.set_mesh_data(mesh_data);

    ui::node sub_node;
    node.push_back_sub_node(sub_node);

    ui::tree_updates updates;

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    node.renderable().fetch_updates(updates);
    XCTAssertFalse(updates.is_any_updated());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    node.set_angle(1.0f);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 1);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::geometry));
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertFalse(updates.mesh_data_updates.flags.any());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    mesh.set_use_mesh_color(true);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertFalse(updates.node_updates.flags.any());
    XCTAssertEqual(updates.mesh_updates.flags.count(), 1);
    XCTAssertTrue(updates.mesh_updates.test(ui::mesh_update_reason::use_mesh_color));
    XCTAssertFalse(updates.mesh_data_updates.flags.any());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    mesh_data.set_vertex_count(1);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertFalse(updates.node_updates.flags.any());
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertEqual(updates.mesh_data_updates.flags.count(), 1);
    XCTAssertTrue(updates.mesh_data_updates.test(ui::mesh_data_update_reason::vertex_count));

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    sub_node.set_enabled(false);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 1);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::enabled));
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertFalse(updates.mesh_data_updates.flags.any());
}

- (void)test_is_rendering_color_exists {<commit_msg>add tests<commit_after>//
//  yas_ui_node_tests.mm
//

#import <XCTest/XCTest.h>
#import <iostream>
#import "yas_objc_ptr.h"
#import "yas_ui.h"
#import "yas_ui_render_info.h"
#import "yas_ui_math.h"

using namespace yas;

namespace yas {
namespace test {
    struct test_render_encoder : base {
        struct impl : base::impl, ui::render_encodable::impl {
            void push_back_mesh(ui::mesh &&mesh) {
                _meshes.emplace_back(std::move(mesh));
            }

            std::vector<ui::mesh> _meshes;
        };

        test_render_encoder() : base(std::make_shared<impl>()) {
        }

        ui::render_encodable encodable() {
            return ui::render_encodable{impl_ptr<ui::render_encodable::impl>()};
        }

        std::vector<ui::mesh> &meshes() {
            return impl_ptr<impl>()->_meshes;
        }
    };
}
}

@interface yas_ui_node_tests : XCTestCase

@end

@implementation yas_ui_node_tests

- (void)setUp {
    [super setUp];
}

- (void)tearDown {
    [super tearDown];
}

- (void)test_create {
    ui::node node;

    XCTAssertEqual(node.position().x, 0.0f);
    XCTAssertEqual(node.position().y, 0.0f);
    XCTAssertEqual(node.angle(), 0.0f);
    XCTAssertEqual(node.radians(), 0.0f);
    XCTAssertEqual(node.scale().width, 1.0f);
    XCTAssertEqual(node.scale().height, 1.0f);

    XCTAssertEqual(node.color().red, 1.0f);
    XCTAssertEqual(node.color().green, 1.0f);
    XCTAssertEqual(node.color().blue, 1.0f);
    XCTAssertEqual(node.alpha(), 1.0f);

    XCTAssertFalse(node.mesh());
    XCTAssertFalse(node.collider());

    XCTAssertEqual(node.children().size(), 0);
    XCTAssertFalse(node.parent());
    XCTAssertFalse(node.renderer());

    XCTAssertTrue(node.is_enabled());

    XCTAssertTrue(node.renderable());
    XCTAssertTrue(node.metal());
}

- (void)test_create_null {
    ui::node node{nullptr};

    XCTAssertFalse(node);
}

- (void)test_set_variables {
    ui::node node;
    ui::mesh mesh;
    ui::collider collider;
    ui::batch batch;

    node.set_position({1.0f, 2.0f});
    node.set_angle(3.0f);
    node.set_scale({4.0f, 5.0f});
    node.set_color({0.1f, 0.2f, 0.3f});
    node.set_alpha(0.4f);

    node.set_enabled(true);

    XCTAssertEqual(node.position().x, 1.0f);
    XCTAssertEqual(node.position().y, 2.0f);
    XCTAssertEqual(node.angle(), 3.0f);
    XCTAssertEqualWithAccuracy(node.radians(), radians_from_degrees(3.0f), 0.001f);
    XCTAssertEqual(node.scale().width, 4.0f);
    XCTAssertEqual(node.scale().height, 5.0f);
    XCTAssertEqual(node.color().red, 0.1f);
    XCTAssertEqual(node.color().green, 0.2f);
    XCTAssertEqual(node.color().blue, 0.3f);
    XCTAssertEqual(node.alpha(), 0.4f);

    node.set_mesh(mesh);
    XCTAssertTrue(node.mesh());
    XCTAssertEqual(node.mesh(), mesh);

    node.set_collider(collider);
    XCTAssertTrue(node.collider());
    XCTAssertEqual(node.collider(), collider);

    node.set_batch(batch);
    XCTAssertTrue(node.batch());
    XCTAssertEqual(node.batch(), batch);

    node.set_batch(nullptr);
    XCTAssertFalse(node.batch());

    XCTAssertTrue(node.is_enabled());
}

- (void)test_const_variables {
    ui::node node;
    ui::node const &const_node = node;

    XCTAssertFalse(const_node.mesh());
    XCTAssertFalse(const_node.collider());
    XCTAssertFalse(const_node.batch());

    node.set_mesh(ui::mesh{});
    node.set_collider(ui::collider{});
    node.set_batch(ui::batch{});

    XCTAssertTrue(const_node.mesh());
    XCTAssertTrue(const_node.collider());
    XCTAssertEqual(const_node.children().size(), 0);
    XCTAssertTrue(const_node.batch());
}

- (void)set_color_to_mesh {
    ui::node node;
    ui::mesh mesh;

    XCTAssertEqual(mesh.color()[0], 1.0f);
    XCTAssertEqual(mesh.color()[1], 1.0f);
    XCTAssertEqual(mesh.color()[2], 1.0f);
    XCTAssertEqual(mesh.color()[3], 1.0f);

    node.set_color({0.25f, 0.5f, 0.75f});
    node.set_alpha(0.125f);

    node.set_mesh(mesh);

    XCTAssertEqual(mesh.color()[0], 0.25f);
    XCTAssertEqual(mesh.color()[1], 0.5f);
    XCTAssertEqual(mesh.color()[2], 0.75f);
    XCTAssertEqual(mesh.color()[3], 0.125f);

    node.set_color({0.1f, 0.2f, 0.3f});
    node.set_alpha(0.4f);

    XCTAssertEqual(mesh.color()[0], 0.1f);
    XCTAssertEqual(mesh.color()[1], 0.2f);
    XCTAssertEqual(mesh.color()[2], 0.3f);
    XCTAssertEqual(mesh.color()[3], 0.4f);
}

- (void)test_is_equal {
    ui::node node1;
    ui::node node1b = node1;
    ui::node node2;

    XCTAssertTrue(node1 == node1);
    XCTAssertTrue(node1 == node1b);
    XCTAssertFalse(node1 == node2);

    XCTAssertFalse(node1 != node1);
    XCTAssertFalse(node1 != node1b);
    XCTAssertTrue(node1 != node2);
}

- (void)test_hierarchie {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;

    XCTAssertEqual(parent_node.children().size(), 0);
    XCTAssertFalse(sub_node1.parent());
    XCTAssertFalse(sub_node2.parent());

    parent_node.push_back_sub_node(sub_node1);

    XCTAssertEqual(parent_node.children().size(), 1);
    XCTAssertTrue(sub_node1.parent());

    parent_node.push_back_sub_node(sub_node2);

    XCTAssertEqual(parent_node.children().size(), 2);
    XCTAssertTrue(sub_node1.parent());
    XCTAssertTrue(sub_node2.parent());

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
    XCTAssertEqual(sub_node1.parent(), parent_node);
    XCTAssertEqual(sub_node2.parent(), parent_node);

    sub_node1.remove_from_super_node();

    XCTAssertEqual(parent_node.children().size(), 1);
    XCTAssertFalse(sub_node1.parent());
    XCTAssertTrue(sub_node2.parent());

    XCTAssertEqual(parent_node.children().at(0), sub_node2);

    sub_node2.remove_from_super_node();

    XCTAssertEqual(parent_node.children().size(), 0);
    XCTAssertFalse(sub_node2.parent());
}

- (void)test_push_front_sub_node {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;

    parent_node.push_back_sub_node(sub_node2);
    parent_node.push_front_sub_node(sub_node1);

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
}

- (void)test_insert_sub_node {
    ui::node parent_node;

    ui::node sub_node1;
    ui::node sub_node2;
    ui::node sub_node3;

    parent_node.push_back_sub_node(sub_node1);
    parent_node.push_back_sub_node(sub_node3);
    parent_node.insert_sub_node(sub_node2, 1);

    XCTAssertEqual(parent_node.children().at(0), sub_node1);
    XCTAssertEqual(parent_node.children().at(1), sub_node2);
    XCTAssertEqual(parent_node.children().at(2), sub_node3);
}

- (void)test_renderable_node {
    ui::node node;
    ui::renderer renderer;

    auto &renderable = node.renderable();

    XCTAssertFalse(renderable.renderer());

    renderable.set_renderer(renderer);

    XCTAssertTrue(renderable.renderer());
    XCTAssertEqual(renderable.renderer(), renderer);
}

- (void)test_add_and_remove_node_observed {
    ui::node parent_node;
    ui::node sub_node;
    int observer_called_count = 0;
    bool added_to_super_called = false;
    bool removed_from_super_called = false;

    auto observer = sub_node.subject().make_wild_card_observer(
        [&observer_called_count, &added_to_super_called, &removed_from_super_called](auto const &context) {
            switch (context.key) {
                case ui::node::method::added_to_super:
                    added_to_super_called = true;
                    break;
                case ui::node::method::removed_from_super:
                    removed_from_super_called = true;
                    break;

                default:
                    break;
            }
            ++observer_called_count;
        });

    parent_node.push_back_sub_node(sub_node);

    XCTAssertTrue(added_to_super_called);

    sub_node.remove_from_super_node();

    XCTAssertTrue(removed_from_super_called);

    XCTAssertEqual(observer_called_count, 2);
}

- (void)test_method_undispatched {
    ui::node node;
    ui::renderer renderer;

    std::shared_ptr<ui::node::method> called_method = nullptr;

    auto observer = node.subject().make_wild_card_observer([&called_method](auto const &context) mutable {
        auto const &method = context.key;
        if (method != ui::node::method::added_to_super && method != ui::node::method::removed_from_super) {
            called_method = std::make_shared<ui::node::method>(context.key);
        }
    });

    node.set_position({1.0f, 2.0f});
    XCTAssertFalse(called_method);
    node.set_angle(90.0f);
    XCTAssertFalse(called_method);
    node.set_scale({3.0f, 4.0f});
    XCTAssertFalse(called_method);
    node.set_color({1.0f, 2.0f, 3.0f});
    XCTAssertFalse(called_method);
    node.set_alpha(5.0f);
    XCTAssertFalse(called_method);
    node.set_enabled(false);
    XCTAssertFalse(called_method);
    node.set_mesh(ui::mesh{});
    XCTAssertFalse(called_method);
    node.set_collider(ui::collider{});
    XCTAssertFalse(called_method);

    ui::node parent;
    parent.push_back_sub_node(node);
    XCTAssertFalse(called_method);

    node.renderable().set_renderer(renderer);
    XCTAssertFalse(called_method);
}

- (void)test_method_dispatched {
    std::shared_ptr<ui::node::method> called_method = nullptr;

    auto make_observer = [&called_method](ui::node &node) {
        return node.subject().make_wild_card_observer([&called_method](auto const &context) mutable {
            auto const &method = context.key;
            if (method != ui::node::method::added_to_super && method != ui::node::method::removed_from_super) {
                called_method = std::make_shared<ui::node::method>(context.key);
            }
        });
    };

    {
        ui::node node;
        node.dispatch_method(ui::node::method::position_changed);
        auto observer = make_observer(node);
        node.set_position({1.0f, 2.0f});
        XCTAssertEqual(*called_method, ui::node::method::position_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::angle_changed);
        auto observer = make_observer(node);
        node.set_angle(90.0f);
        XCTAssertEqual(*called_method, ui::node::method::angle_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::scale_changed);
        auto observer = make_observer(node);
        node.set_scale({3.0f, 4.0f});
        XCTAssertEqual(*called_method, ui::node::method::scale_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::color_changed);
        auto observer = make_observer(node);
        node.set_color({1.0f, 2.0f, 3.0f});
        XCTAssertEqual(*called_method, ui::node::method::color_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::alpha_changed);
        auto observer = make_observer(node);
        node.set_alpha(0.5f);
        XCTAssertEqual(*called_method, ui::node::method::alpha_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::enabled_changed);
        auto observer = make_observer(node);
        node.set_enabled(false);
        XCTAssertEqual(*called_method, ui::node::method::enabled_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::mesh_changed);
        auto observer = make_observer(node);
        node.set_mesh(ui::mesh{});
        XCTAssertEqual(*called_method, ui::node::method::mesh_changed);
    }

    called_method = nullptr;

    {
        ui::node node;
        node.dispatch_method(ui::node::method::collider_changed);
        auto observer = make_observer(node);
        node.set_collider(ui::collider{});
        XCTAssertEqual(*called_method, ui::node::method::collider_changed);
    }

    called_method = nullptr;

    {
        ui::node parent;
        ui::node node;
        node.dispatch_method(ui::node::method::parent_changed);
        auto observer = make_observer(node);
        parent.push_back_sub_node(node);
        XCTAssertEqual(*called_method, ui::node::method::parent_changed);
    }

    called_method = nullptr;

    {
        ui::renderer renderer;
        ui::node node;
        node.dispatch_method(ui::node::method::renderer_changed);
        auto observer = make_observer(node);
        node.renderable().set_renderer(renderer);
        XCTAssertEqual(*called_method, ui::node::method::renderer_changed);
    }

    called_method = nullptr;
}

- (void)test_fetch_updates {
    ui::node node;

    ui::mesh mesh;
    node.set_mesh(mesh);

    ui::dynamic_mesh_data mesh_data{{.vertex_count = 2, .index_count = 2}};
    mesh.set_mesh_data(mesh_data);

    ui::node sub_node;
    node.push_back_sub_node(sub_node);

    ui::tree_updates updates;

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    node.renderable().fetch_updates(updates);
    XCTAssertFalse(updates.is_any_updated());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    node.set_angle(1.0f);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 1);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::geometry));
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertFalse(updates.mesh_data_updates.flags.any());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    mesh.set_use_mesh_color(true);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertFalse(updates.node_updates.flags.any());
    XCTAssertEqual(updates.mesh_updates.flags.count(), 1);
    XCTAssertTrue(updates.mesh_updates.test(ui::mesh_update_reason::use_mesh_color));
    XCTAssertFalse(updates.mesh_data_updates.flags.any());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    mesh_data.set_vertex_count(1);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertFalse(updates.node_updates.flags.any());
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertEqual(updates.mesh_data_updates.flags.count(), 1);
    XCTAssertTrue(updates.mesh_data_updates.test(ui::mesh_data_update_reason::vertex_count));

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    sub_node.set_enabled(false);
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 1);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::enabled));
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertFalse(updates.mesh_data_updates.flags.any());
}

- (void)test_fetch_updates_when_enabled_changed {
    ui::node node;

    ui::tree_updates updates;

    updates = ui::tree_updates{};
    node.renderable().clear_updates();
    node.renderable().fetch_updates(updates);
    XCTAssertFalse(updates.is_any_updated());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    // nodeのパラメータを変更する
    node.set_mesh(ui::mesh{});
    ui::dynamic_mesh_data mesh_data{{.vertex_count = 2, .index_count = 2}};
    mesh_data.set_vertex_count(1);
    node.mesh().set_mesh_data(mesh_data);

    node.set_angle(1.0f);
    node.set_enabled(false);
    node.set_collider(ui::collider{});
    node.set_batch(ui::batch{});

    ui::node sub_node;
    node.push_back_sub_node(sub_node);

    // enabledがfalseの時はenabled以外の変更はフェッチされない
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 1);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::enabled));
    XCTAssertFalse(updates.mesh_updates.flags.any());
    XCTAssertFalse(updates.mesh_data_updates.flags.any());

    updates = ui::tree_updates{};
    node.renderable().clear_updates();

    node.set_enabled(true);

    // enabledをtrueにするとフェッチされる
    node.renderable().fetch_updates(updates);
    XCTAssertTrue(updates.is_any_updated());
    XCTAssertEqual(updates.node_updates.flags.count(), 6);
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::enabled));
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::children));
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::geometry));
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::mesh));
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::collider));
    XCTAssertTrue(updates.node_updates.test(ui::node_update_reason::batch));
    XCTAssertTrue(updates.mesh_updates.flags.any());
    XCTAssertTrue(updates.mesh_data_updates.flags.any());
}

- (void)test_is_rendering_color_exists {
{'fraction_non_alphanumeric': 0.14742606790799562, 'fraction_numerical': 0.015052417462056017, 'mean_word_length': 3.8054135338345865, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1183877', 'n_tokens_mistral': 11515, 'n_tokens_neox': 11128, 'n_words': 1275}