#include <api.h>

typedef struct Rel {
    float value;
} Rel;

typedef struct RelA {
    float value;
} RelA;

typedef struct RelB {
    float value;
} RelB;

typedef struct Obj {
    float value;
} Obj;

void ProcessPairs(ecs_iter_t *it) {
    Rel *tr = ecs_field(it, Rel, 1);

    probe_iter(it);

    if (tr) {
        int32_t i;
        for (i = 0; i < it->count; i ++) {
            tr[i].value ++;
        }
    }
}

void Pairs_type_w_one_pair(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *));

    /* Ensure that pair is matched against different components */
    ecs_entity_t e1 = ecs_set_pair(world, 0, Rel, ecs_id(Position), {
        .value = 10
    });

    test_assert(e1 != 0);
    test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position)));

    ecs_entity_t e2 = ecs_set_pair(world, 0, Rel, ecs_id(Velocity), {
        .value = 20
    });
    test_assert(e2 != 0);
    test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Velocity)));

    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);

    ecs_progress(world, 0);

    const Rel* tr = ecs_get_pair(world, e1, Rel, ecs_id(Position));
    test_assert(tr != NULL);
    test_int(tr->value, 11);

    tr = ecs_get_pair(world, e2, Rel, ecs_id(Velocity));
    test_assert(tr != NULL);
    test_int(tr->value, 21); 

    test_int(ctx.count, 2);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, ProcessPairs);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Position));

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Velocity));

    test_int(ctx.s[0][0], 0);
    test_int(ctx.s[1][0], 0);

    ecs_fini(world);
}

void Pairs_type_w_two_pairs(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ECS_SYSTEM(world, ProcessPairs, EcsOnUpdate, (Rel, *));

    /* Ensure that pair is matched against different components on same entity */
    ecs_entity_t e1 = ecs_new(world, 0);
    ecs_set_pair(world, e1, Rel, ecs_id(Position), {
        .value = 10
    });  
    test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position)));

    ecs_set_pair(world, e1, Rel, ecs_id(Velocity), {
        .value = 20
    });      
    test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Velocity)));

    ecs_entity_t e2 = ecs_new(world, 0);
    ecs_set_pair(world, e2, Rel, ecs_id(Position), {
        .value = 30
    });
    test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Position)));

    ecs_set_pair(world, e2, Rel, ecs_id(Velocity), {
        .value = 40
    });
    test_assert( ecs_has_pair(world, e2, ecs_id(Rel), ecs_id(Position)));

    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);

    ecs_progress(world, 0);

    const Rel* tr = ecs_get_pair(world, e1, Rel, ecs_id(Position));
    test_assert(tr != NULL);
    test_int(tr->value, 11);

    tr = ecs_get_pair(world, e1, Rel, ecs_id(Velocity));
    test_assert(tr != NULL);
    test_int(tr->value, 21);

    tr = ecs_get_pair(world, e2, Rel, ecs_id(Position));
    test_assert(tr != NULL);
    test_int(tr->value, 31);

    tr = ecs_get_pair(world, e2, Rel, ecs_id(Velocity));
    test_assert(tr != NULL);
    test_int(tr->value, 41); 

    test_int(ctx.count, 4);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, ProcessPairs);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);
    test_int(ctx.e[2], e1);
    test_int(ctx.e[3], e2);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Position));

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Velocity));

    test_int(ctx.s[0][0], 0);
    test_int(ctx.s[1][0], 0);
    test_int(ctx.s[2][0], 0);
    test_int(ctx.s[3][0], 0);

    ecs_fini(world);
}

void Pairs_add_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ecs_entity_t e1 = ecs_new(world, 0);
    test_assert(e1 != 0);

    ecs_add_pair(world, e1, ecs_id(Rel), ecs_id(Position));
    test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position)));

    ecs_fini(world);
}

void Pairs_remove_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ecs_entity_t e1 = ecs_set_pair(world, 0, Rel, ecs_id(Position), {
        .value = 10
    });  
    test_assert(e1 != 0);
    test_assert( ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position)));

    ecs_remove_pair(world, e1, ecs_id(Rel), ecs_id(Position));
    test_assert( !ecs_has_pair(world, e1, ecs_id(Rel), ecs_id(Position)));

    ecs_fini(world);
}

void ProcessPairTags(ecs_iter_t *it) {
    probe_iter(it);
}

void Pairs_add_tag_pair_for_tag(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag1);
    ECS_TAG(world, Tag2);
    ECS_TAG(world, Rel);

    ECS_SYSTEM(world, ProcessPairTags, EcsOnUpdate, (Rel, *));

    /* Ensure that pair is matched against different components */
    ecs_entity_t e1 = ecs_new(world, 0);
    ecs_add_pair(world, e1, Rel, Tag1);
    test_assert(e1 != 0);
    test_assert( ecs_has_pair(world, e1, Rel, Tag1));

    ecs_entity_t e2 = ecs_new(world, 0);
    ecs_add_pair(world, e2, Rel, Tag2);
    test_assert(e2 != 0);
    test_assert( ecs_has_pair(world, e2, Rel, Tag2));

    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);

    ecs_progress(world, 0);

    test_int(ctx.count, 2);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, ProcessPairTags);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_entity_t c = ctx.c[0][0];

    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, Tag1);

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, Tag2);

    test_int(ctx.s[0][0], 0);
    test_int(ctx.s[1][0], 0);

    ecs_fini(world);
}

void ProcessValuePairs(ecs_iter_t *it) {
    /* Strictly speaking this can be either Position or Velocity, but they have
     * the same layout. */
    Position *p = ecs_field(it, Position, 1);

    probe_iter(it);

    if (p) {
        int32_t i;
        for (i = 0; i < it->count; i ++) {
            p[i].x += 10;
            p[i].y += 20;
        }
    }
}

void Pairs_add_tag_pair_for_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_TAG(world, Rel);

    ECS_SYSTEM(world, ProcessValuePairs, EcsOnUpdate, (Rel, *));

    ecs_entity_t e1 = ecs_set_pair_object(world, 0, Rel, Position, {
        .x = 1,
        .y = 2
    });
    test_assert( ecs_has_pair(world, e1, Rel, ecs_id(Position)));

    ecs_entity_t e2 = ecs_set_pair_object(world, 0, Rel, Velocity, {
        .x = 3,
        .y = 4
    });
    test_assert( ecs_has_pair(world, e2, Rel, ecs_id(Velocity)));

    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);

    ecs_progress(world, 0);

    test_int(ctx.count, 2);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, ProcessValuePairs);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e2);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Position));

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Velocity));

    test_int(ctx.s[0][0], 0);
    test_int(ctx.s[1][0], 0);

    ecs_fini(world);
}

void ProcessTwoPairs(ecs_iter_t *it) {
    RelA *tr_a = ecs_field(it, RelA, 1);
    RelB *tr_b = ecs_field(it, RelB, 2);

    probe_iter(it);

    int32_t i;
    for (i = 0; i < it->count; i ++) {
        tr_a[i].value ++;
        tr_b[i].value ++;
    }
}

void Pairs_query_2_pairs(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, RelA);
    ECS_COMPONENT(world, RelB);

    ECS_SYSTEM(world, ProcessTwoPairs, EcsOnUpdate, (RelA, *), (RelB, *));

    /* Create entity with both RelA and RelB */
    ecs_entity_t e1 = ecs_set_pair(world, 0, RelA, ecs_id(Position), {
        .value = 1
    });

    ecs_set_pair(world, e1, RelB, ecs_id(Position), {
        .value = 2
    });

    test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Position)));
    test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Position)));

    /* Create entity with only RelA. Should not be matched with system */
    ecs_entity_t e2 = ecs_set_pair(world, 0, RelA, ecs_id(Position), {
        .value = 3
    });
    test_assert( ecs_has_pair(world, e2, ecs_id(RelA), ecs_id(Position)));

    /* Run system */
    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);
    ecs_progress(world, 0);

    const RelA *tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Position));
    test_int(tr_a->value, 2);

    const RelB *tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Position));
    test_int(tr_b->value, 3);

    tr_a = ecs_get_pair(world, e2, RelA, ecs_id(Position));
    test_int(tr_a->value, 3);

    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, ProcessTwoPairs);
    test_int(ctx.term_count, 2);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(RelA));
    test_int(lo, ecs_id(Position));

    c = ctx.c[0][1];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(RelB));
    test_int(lo, ecs_id(Position));    

    ecs_fini(world);
}

void Pairs_query_2_pairs_2_instances_per_type(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, RelA);
    ECS_COMPONENT(world, RelB);

    ECS_SYSTEM(world, ProcessTwoPairs, EcsOnUpdate, (RelA, *), (RelB, *));

    /* Create entity with both RelA and RelB, applied to two components*/
    ecs_entity_t e1 = ecs_set_pair(world, 0, RelA, ecs_id(Position), {
        .value = 1
    });

    ecs_set_pair(world, e1, RelB, ecs_id(Position), {
        .value = 2
    });

    ecs_set_pair(world, e1, RelA, ecs_id(Velocity), {
        .value = 3
    });

    ecs_set_pair(world, e1, RelB, ecs_id(Velocity), {
        .value = 4
    });        

    test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Position)));
    test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Position)));
    test_assert( ecs_has_pair(world, e1, ecs_id(RelA), ecs_id(Velocity)));
    test_assert( ecs_has_pair(world, e1, ecs_id(RelB), ecs_id(Velocity)));

    /* Run system */
    Probe ctx = {0};
    ecs_set_ctx(world, &ctx, NULL);
    ecs_progress(world, 0);

    const RelA *tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Position));
    test_int(tr_a->value, 3);

    tr_a = ecs_get_pair(world, e1, RelA, ecs_id(Velocity));
    test_int(tr_a->value, 5);

    const RelB *tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Position));
    test_int(tr_b->value, 4);

    tr_b = ecs_get_pair(world, e1, RelB, ecs_id(Velocity));
    test_int(tr_b->value, 6);

    test_int(ctx.count, 4);
    test_int(ctx.invoked, 4);
    test_int(ctx.system, ProcessTwoPairs);
    test_int(ctx.term_count, 2);
    test_null(ctx.param);

    test_int(ctx.e[0], e1);
    test_int(ctx.e[1], e1);
    test_int(ctx.e[2], e1);
    test_int(ctx.e[3], e1);

    ecs_fini(world);
}

void Pairs_override_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Rel);

    ecs_entity_t base = ecs_new(world, 0);
    ecs_set_pair(world, base, Rel, ecs_id(Position), {.value = 10});

    ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base);
    test_assert(ecs_has_pair(world, instance, ecs_id(Rel), ecs_id(Position)));

    const Rel *t = ecs_get_pair(world, instance, Rel, ecs_id(Position));
    test_assert(t != NULL);
    test_int(t->value, 10);

    const Rel *t_2 = ecs_get_pair(world, base, Rel, ecs_id(Position));
    test_assert(t_2 != NULL);
    test_assert(t == t_2);

    ecs_add_pair(world, instance, ecs_id(Rel), ecs_id(Position));
    t = ecs_get_pair(world, instance, Rel, ecs_id(Position));
    test_assert(t != NULL);
    test_int(t->value, 10);
    test_assert(t != t_2);

    ecs_fini(world);
}

void Pairs_override_tag_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Rel);

    ecs_entity_t base = ecs_new(world, 0);
    ecs_set_pair_object(world, base, Rel, Position, {.x = 10, .y = 20});

    ecs_entity_t instance = ecs_new_w_pair(world, EcsIsA, base);
    test_assert(ecs_has_pair(world, instance, Rel, ecs_id(Position)));

    const Position *t = ecs_get_pair_object(world, instance, Rel, Position);
    test_assert(t != NULL);
    test_int(t->x, 10);
    test_int(t->y, 20);

    const Position *t_2 = ecs_get_pair_object(world, base, Rel, Position);
    test_assert(t_2 != NULL);
    test_assert(t == t_2);

    ecs_add_pair(world, instance, Rel, ecs_id(Position));
    t = ecs_get_pair_object(world, instance, Rel, Position);
    test_assert(t != NULL);
    test_int(t->x, 10);
    test_int(t->y, 20);
    test_assert(t != t_2);

    ecs_fini(world);
}

static
void PairTrigger(ecs_iter_t *it) {
    probe_iter(it);
}

void Pairs_on_add_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ECS_OBSERVER(world, PairTrigger, EcsOnAdd, (Rel, *));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position));

    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Position));  

    ecs_os_zeromem(&ctx);
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity));

    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    c = ctx.c[0][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Velocity));

    ecs_fini(world);
}

void Pairs_on_add_pair_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Rel);

    ECS_OBSERVER(world, PairTrigger, EcsOnAdd, (Rel, Position));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, Rel, ecs_id(Position));

    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Position));  

    ecs_fini(world);
}

void Pairs_on_remove_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ECS_OBSERVER(world, PairTrigger, EcsOnRemove, (Rel, *));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position));
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity));
    test_int(ctx.count, 0);

    ecs_remove_pair(world, e, ecs_id(Rel), ecs_id(Position));
    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Position));  

    ecs_os_zeromem(&ctx);
    ecs_remove_pair(world, e, ecs_id(Rel), ecs_id(Velocity));
    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    c = ctx.c[0][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Velocity));

    ecs_fini(world);
}

void Pairs_on_remove_pair_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Rel);

    ECS_OBSERVER(world, PairTrigger, EcsOnRemove, (Rel, Position));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, Rel, ecs_id(Position));
    test_int(ctx.count, 0);

    ecs_remove_pair(world, e, Rel, ecs_id(Position));
    test_int(ctx.count, 1);
    test_int(ctx.invoked, 1);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Position));  

    ecs_fini(world);
}

void Pairs_on_remove_pair_on_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Rel);

    ECS_OBSERVER(world, PairTrigger, EcsOnRemove, (Rel, *));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position));
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Velocity));
    test_int(ctx.count, 0);

    ecs_delete(world, e);
    test_int(ctx.count, 2);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, PairTrigger);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Position));  

    test_int(ctx.e[1], e);

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, ecs_id(Rel));
    test_int(lo, ecs_id(Velocity));

    ecs_fini(world);
}

static
void PairTriggerPosition(ecs_iter_t *it) {
    PairTrigger(it);
}

static
void PairTriggerVelocity(ecs_iter_t *it) {
    PairTrigger(it);
}

void Pairs_on_remove_pair_tag_on_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_TAG(world, Rel);

    ECS_OBSERVER(world, PairTriggerPosition, EcsOnRemove, (Rel, Position));
    ECS_OBSERVER(world, PairTriggerVelocity, EcsOnRemove, (Rel, Velocity));

    Probe ctx = { 0 };
    ecs_set_ctx(world, &ctx, NULL);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, Rel, ecs_id(Position));
    ecs_add_pair(world, e, Rel, ecs_id(Velocity));
    test_int(ctx.count, 0);

    ecs_delete(world, e);
    test_int(ctx.count, 2);
    test_int(ctx.invoked, 2);
    test_int(ctx.system, PairTriggerVelocity);
    test_int(ctx.term_count, 1);
    test_null(ctx.param);
    test_int(ctx.e[0], e);

    ecs_entity_t c = ctx.c[0][0];
    ecs_entity_t hi = ECS_PAIR_FIRST(c);
    ecs_entity_t lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Position));  

    test_int(ctx.e[1], e);

    c = ctx.c[1][0];
    hi = ECS_PAIR_FIRST(c);
    lo = ECS_PAIR_SECOND(c);
    test_int(hi, Rel);
    test_int(lo, ecs_id(Velocity));

    ecs_fini(world);
}

void Pairs_pair_w_component_query(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Rel);

    ecs_entity_t e = ecs_new(world, 0);
    ecs_add_pair(world, e, ecs_id(Rel), ecs_id(Position));

    ecs_query_t *q = ecs_query_new(world, "(Rel, Position)");
    
    int32_t count = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        Rel *t = ecs_field(&it, Rel, 1);
        test_assert(t != NULL);

        int i;
        for (i = 0; i < it.count; i ++) {
            test_assert(it.entities[i] == e);
        }
        count += it.count;
    }

    test_assert(count == 1);

    ecs_fini(world);
}

void Pairs_query_pair_or_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Rel);

    ecs_entity_t e1 = ecs_new(world, 0);
    ecs_add_pair(world, e1, Rel, ecs_id(Position));
    ecs_entity_t e2 = ecs_new(world, Position);

    ecs_query_t *q = ecs_query_new(world, "(Rel, Position) || Position");
    
    int32_t count = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        int i;
        for (i = 0; i < it.count; i ++) {
            test_assert(it.entities[i] == e1 || it.entities[i] == e2);
        }
        count += it.count;
    }

    test_assert(count == 2);

    ecs_fini(world);
}

void Pairs_query_pair_or_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);

    ecs_entity_t e1 = ecs_new(world, 0);
    ecs_add_pair(world, e1, RelA, ecs_id(Position));
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_add_pair(world, e2, RelB, ecs_id(Position));

    ecs_query_t *q = ecs_query_new(world, "(RelA, Position) || (RelB, Position)");
    
    int32_t count = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        int i;
        for (i = 0; i < it.count; i ++) {
            test_assert(it.entities[i] == e1 || it.entities[i] == e2);
        }
        count += it.count;
    }

    test_int(count, 2);

    ecs_fini(world);
}

void Pairs_query_not_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Rel);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_add_pair(world, e2, Rel, ecs_id(Position));

    ecs_query_t *q = ecs_query_new(world, "!(Rel, Position), Position");
    
    int32_t count = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        Position *t = ecs_field(&it, Position, 1);
        test_assert(t == NULL);
        Position *p = ecs_field(&it, Position, 2);
        test_assert(p != NULL);

        int i;
        for (i = 0; i < it.count; i ++) {
            test_assert(it.entities[i] == e1);
        }
        count += it.count;
    }

    test_assert(count == 1);

    ecs_fini(world);
}

void Pairs_get_typeid_w_recycled_rel(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t o = ecs_new_id(world);
    test_assert(o != 0);

    ecs_entity_t dummy = ecs_new_id(world);
    ecs_delete(world, dummy); // force recycle

    // don't use ECS_COMPONENT, because it will try to get low ids first
    ecs_entity_t pos = ecs_set(world, 0, EcsComponent, {1, 1});
    test_assert(pos != 0);
    test_assert((uint32_t)pos != pos); // ensure recycled

    ecs_id_t pair = ecs_pair(pos, o);
    ecs_entity_t tid = ecs_get_typeid(world, pair);
    test_assert(tid != 0);
    test_assert(tid == pos);

    ecs_fini(world);
}

void Pairs_get_typeid_w_recycled_obj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t r = ecs_new_id(world);
    test_assert(r != 0);

    ecs_entity_t dummy = ecs_new_id(world);
    ecs_delete(world, dummy); // force recycle

    // don't use ECS_COMPONENT, because it will try to get low ids first
    ecs_entity_t pos = ecs_set(world, 0, EcsComponent, {1, 1});
    test_assert(pos != 0);
    test_assert((uint32_t)pos != pos); // ensure recycled

    ecs_id_t pair = ecs_pair(r, pos);
    ecs_entity_t tid = ecs_get_typeid(world, pair);
    test_assert(tid != 0);
    test_assert(tid == pos);

    ecs_fini(world);
}

void Pairs_id_str_w_recycled_rel(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t o = ecs_set_name(world, 0, "o");
    test_assert(o != 0);

    ecs_entity_t dummy = ecs_new_id(world);
    ecs_delete(world, dummy); // force recycle

    ecs_entity_t r = ecs_set_name(world, 0, "r");
    test_assert(r != 0);
    test_assert((uint32_t)r != r); // ensure recycled

    char *str = ecs_id_str(world, ecs_pair(r, o));
    test_str(str, "(r,o)");
    ecs_os_free(str);

    ecs_fini(world);
}

void Pairs_id_str_w_recycled_obj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t r = ecs_set_name(world, 0, "r");
    test_assert(r != 0);

    ecs_entity_t dummy = ecs_new_id(world);
    ecs_delete(world, dummy); // force recycle

    ecs_entity_t o = ecs_set_name(world, 0, "o");
    test_assert(o != 0);
    test_assert((uint32_t)o != o); // ensure recycled

    char *str = ecs_id_str(world, ecs_pair(r, o));
    test_str(str, "(r,o)");
    ecs_os_free(str);

    ecs_fini(world);
}

void Pairs_set_object_w_zero_sized_rel_comp(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t r = ecs_set(world, 0, EcsComponent, {0});

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_set_pair_object(world, 0, r, Position, {10, 20});
    
    const Position *p = ecs_get_pair_object(world, e, r, Position);
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);

    ecs_fini(world);
}

void Pairs_dsl_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "(Rel, Obj)"
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_dsl_pair_w_pred_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "(*, Obj)"
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_dsl_pair_w_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "(Rel_2, *)"
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj_2));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_dsl_pair_w_both_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ECS_TAG(world, Tag);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "(*, *), Tag" // add Tag or we'd match builtin entities
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj), Tag} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2), Tag} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj), Tag} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2), Tag} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);

    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e2);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj_2));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj_2));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_dsl_pair_w_explicit_subj_this(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "Rel($This, Obj)"
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_dsl_pair_w_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ECS_TAG(world, Tag);

    ecs_entity_t Subj = ecs_entity_init(world, &(ecs_entity_desc_t){
        .name = "Subj", .add = {ecs_pair(Rel, Obj)} });
    test_assert(Subj != 0);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.expr = "Rel(Subj, Obj), Tag"
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2), Tag} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));
    test_int(ecs_field_src(&it, 1), Subj);

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {{ecs_pair(Rel, Obj)}}
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair_w_pred_wildcard(void) {
   ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {{ecs_pair(EcsWildcard, Obj)}}
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair_w_obj_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {{ecs_pair(Rel_2, EcsWildcard)}}
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj_2));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair_w_both_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ECS_TAG(world, Tag);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        // add Tag or we'd match builtin entities
        .filter.terms = {{ecs_pair(EcsWildcard, EcsWildcard)}, {Tag}} 
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj), Tag} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2), Tag} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj), Tag} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2), Tag} });
    test_assert(e4 != 0);    

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e2);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj_2));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e3);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj));

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel_2, Obj_2));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair_w_explicit_subj_this(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {
            { ecs_pair(Rel, Obj), .src.id = EcsThis }
        }
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2)} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e1);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_api_pair_w_explicit_subj(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Rel_2);
    ECS_TAG(world, Obj);
    ECS_TAG(world, Obj_2);

    ECS_TAG(world, Tag);

    ecs_entity_t Subj = ecs_entity_init(world, &(ecs_entity_desc_t){
        .name = "Subj", .add = {ecs_pair(Rel, Obj)} });
    test_assert(Subj != 0);

    ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){
        .filter.terms = {{ecs_pair(Rel, Obj), .src.id = Subj}, {Tag}}
    });

    test_assert(q != NULL);

    ecs_entity_t e1 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj)} });
    test_assert(e1 != 0);

    ecs_entity_t e2 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel, Obj_2)} });
    test_assert(e2 != 0);

    ecs_entity_t e3 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj)} });
    test_assert(e3 != 0);

    ecs_entity_t e4 = ecs_entity_init(world, &(ecs_entity_desc_t){
        .add = {ecs_pair(Rel_2, Obj_2), Tag} });
    test_assert(e4 != 0);

    ecs_iter_t it = ecs_query_iter(world, q);

    test_bool(ecs_query_next(&it), true);
    test_int(it.count, 1);
    test_int(it.entities[0], e4);
    test_int(ecs_field_id(&it, 1), ecs_pair(Rel, Obj));
    test_int(ecs_field_src(&it, 1), Subj);

    test_bool(ecs_query_next(&it), false); 

    ecs_fini(world);
}

void Pairs_typeid_from_tag(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t tag = ecs_new_id(world);
    test_assert(tag != 0);

    ecs_entity_t id = ecs_get_typeid(world, tag);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_typeid_from_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ecs_entity_t id = ecs_get_typeid(world, rel_id);
    test_assert(id != 0);
    test_assert(id == rel_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel_id = ecs_new_id(world);
    test_assert(rel_id != 0);

    ecs_entity_t obj_id = ecs_new_id(world);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_rel_type(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ecs_entity_t obj_id = ecs_new_id(world);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id != 0);
    test_assert(id == rel_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_obj_type(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Obj);
    ecs_id_t obj_id = ecs_id(Obj);
    test_assert(obj_id != 0);

    ecs_entity_t rel_id = ecs_new_id(world);
    test_assert(rel_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id != 0);
    test_assert(id == obj_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_rel_obj_type(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ECS_COMPONENT(world, Obj);
    ecs_id_t obj_id = ecs_id(Obj);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id != 0);
    test_assert(id == rel_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_rel_0_obj_type(void) {
    ecs_world_t *world = ecs_mini();

    ecs_id_t rel_id = ecs_set(world, 0, EcsComponent, {0});
    test_assert(rel_id != 0);

    ECS_COMPONENT(world, Obj);
    ecs_id_t obj_id = ecs_id(Obj);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id != 0);
    test_assert(id == obj_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_rel_obj_0_type(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ecs_id_t obj_id = ecs_set(world, 0, EcsComponent, {0});
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id != 0);
    test_assert(id == rel_id);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_rel_0_obj_0_type(void) {
    ecs_world_t *world = ecs_mini();

    ecs_id_t rel_id = ecs_set(world, 0, EcsComponent, {0});
    test_assert(rel_id != 0);

    ecs_id_t obj_id = ecs_set(world, 0, EcsComponent, {0});
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_with_0_obj(void) {
    ecs_world_t *world = ecs_mini();

    ecs_id_t pair_id = ecs_pair(EcsChildOf, 0);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_override(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ecs_entity_t obj_id = ecs_new_id(world);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ECS_OVERRIDE | ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_typeid_from_pair_w_toggle(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Rel);
    ecs_id_t rel_id = ecs_id(Rel);
    test_assert(rel_id != 0);

    ecs_entity_t obj_id = ecs_new_id(world);
    test_assert(obj_id != 0);

    ecs_id_t pair_id = ECS_TOGGLE | ecs_pair(rel_id, obj_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_tag_pair_w_rel_comp(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    ecs_add_id(world, ecs_id(Position), EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(ecs_id(Position), Obj);

    ecs_add_id(world, e, pair_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_tag_pair_w_obj_comp(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_COMPONENT(world, Position);

    ecs_add_id(world, Rel, EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(Rel, ecs_id(Position));

    ecs_add_id(world, e, pair_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_tag_pair_w_rel_obj_comp(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_add_id(world, ecs_id(Position), EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity));

    ecs_add_id(world, e, pair_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_get_tag_pair_w_rel_comp(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Obj);

    ecs_add_id(world, ecs_id(Position), EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(ecs_id(Position), Obj);

    ecs_add_id(world, e, pair_id);

    test_expect_abort();
    ecs_get_id(world, e, pair_id);
}

void Pairs_get_tag_pair_w_obj_comp(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_add_id(world, ecs_id(Position), EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity));

    ecs_add_id(world, e, pair_id);

    test_expect_abort();
    ecs_get_id(world, e, pair_id);
}

void Pairs_get_tag_pair_w_rel_obj_comp(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_add_id(world, ecs_id(Position), EcsTag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(ecs_id(Position), ecs_id(Velocity));

    ecs_add_id(world, e, pair_id);

    test_expect_abort();
    ecs_get_id(world, e, pair_id);
}

void Pairs_tag_pair_w_childof_w_comp(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(EcsChildOf, ecs_id(Position));

    ecs_add_id(world, e, pair_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_tag_pair_w_isa_w_comp(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new_id(world);
    ecs_id_t pair_id = ecs_pair(EcsIsA, ecs_id(Position));

    ecs_add_id(world, e, pair_id);

    ecs_entity_t id = ecs_get_typeid(world, pair_id);
    test_assert(id == 0);

    ecs_fini(world);
}

void Pairs_get_1_target(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t obj = ecs_new_id(world);

    ecs_add_pair(world, e, rel, obj);
    test_assert(ecs_has_pair(world, e, rel, obj));
    test_assert(ecs_get_target(world, e, rel, 0) == obj);

    ecs_fini(world);
}

void Pairs_get_1_target_not_found(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t rel = ecs_new_id(world);

    test_assert(ecs_get_target(world, e, rel, 0) == 0);

    ecs_fini(world);
}

void Pairs_get_n_targets(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t obj_1 = ecs_new_id(world);
    ecs_entity_t obj_2 = ecs_new_id(world);
    ecs_entity_t obj_3 = ecs_new_id(world);

    ecs_add_pair(world, e, rel, obj_1);
    ecs_add_pair(world, e, rel, obj_2);
    ecs_add_pair(world, e, rel, obj_3);
    test_assert(ecs_has_pair(world, e, rel, obj_1));
    test_assert(ecs_has_pair(world, e, rel, obj_2));
    test_assert(ecs_has_pair(world, e, rel, obj_3));
    test_assert(ecs_get_target(world, e, rel, 0) == obj_1);
    test_assert(ecs_get_target(world, e, rel, 1) == obj_2);
    test_assert(ecs_get_target(world, e, rel, 2) == obj_3);
    test_assert(ecs_get_target(world, e, rel, 3) == 0);

    ecs_fini(world);
}

void Pairs_get_target_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t tgt = ecs_new_id(world);
    ecs_entity_t base = ecs_new_w_pair(world, rel, tgt);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_assert(ecs_get_target(world, base, rel, 0) == tgt);
    test_assert(ecs_get_target(world, inst, rel, 0) == tgt);

    ecs_fini(world);
}

void Pairs_get_target_from_2nd_base(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t tgt = ecs_new_id(world);
    ecs_entity_t base_1 = ecs_new_id(world);
    ecs_entity_t base_2 = ecs_new_w_pair(world, rel, tgt);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base_1);
    ecs_add_pair(world, inst, EcsIsA, base_2);

    test_assert(ecs_get_target(world, base_1, rel, 0) == 0);
    test_assert(ecs_get_target(world, base_2, rel, 0) == tgt);
    test_assert(ecs_get_target(world, inst, rel, 0) == tgt);

    ecs_fini(world);
}

void Pairs_get_target_from_base_w_pair_on_instance(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t tgt_a = ecs_new_id(world);
    ecs_entity_t tgt_b = ecs_new_id(world);
    ecs_entity_t base = ecs_new_w_pair(world, rel, tgt_b);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);
    ecs_add_pair(world, inst, rel, tgt_a);

    test_assert(ecs_get_target(world, base, rel, 0) == tgt_b);
    test_assert(ecs_get_target(world, inst, rel, 0) == tgt_a);
    test_assert(ecs_get_target(world, inst, rel, 1) == tgt_b);

    ecs_fini(world);
}

void Pairs_get_childof_target_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t base = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    test_assert(ecs_has_pair(world, base, EcsChildOf, parent));
    test_assert(!ecs_has_pair(world, e, EcsChildOf, parent));
    test_assert(ecs_get_target(world, e, EcsChildOf, 0) == 0);
    test_assert(ecs_get_target(world, e, EcsChildOf, 1) == 0);

    ecs_fini(world);
}

void Pairs_get_dontinherit_target_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, DontInherit);

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t base = ecs_new_w_pair(world, Rel, parent);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    test_assert(ecs_has_pair(world, base, Rel, parent));
    test_assert(!ecs_has_pair(world, e, Rel, parent));
    test_assert(ecs_get_target(world, e, Rel, 0) == 0);
    test_assert(ecs_get_target(world, e, Rel, 1) == 0);

    ecs_fini(world);
}

void Pairs_get_parent(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, parent);

    test_assert(ecs_get_target(world, e, EcsChildOf, 0) == parent);
    test_assert(ecs_get_parent(world, e) == parent);

    ecs_fini(world);
}

void Pairs_get_parent_from_root(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);

    test_assert(ecs_get_parent(world, e) == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    ecs_add_id(world, e, Tag);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, Tag);
    test_assert(result != 0);
    test_assert(result == e);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, Tag);
    test_assert(result != 0);
    test_assert(result == base);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_nested_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t base_2 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_2);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, Tag);
    test_assert(result != 0);
    test_assert(result == base);

    ecs_fini(world);
}

void Pairs_get_target_for_id_not_found(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new_id(world);
    ecs_entity_t base_2 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_2);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, Tag);
    test_assert(result == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_wildcard_from_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_id_t pair = ecs_pair(Rel, Obj);
    ecs_entity_t base = ecs_new_w_id(world, pair);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    ecs_add_id(world, e, pair);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, 
        ecs_pair(Rel, EcsWildcard));
    test_assert(result != 0);
    test_assert(result == e);

    ecs_fini(world);
}

void Pairs_get_target_for_wildcard_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_id_t pair = ecs_pair(Rel, Obj);
    ecs_entity_t base = ecs_new_w_id(world, pair);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, 
        ecs_pair(Rel, EcsWildcard));
    test_assert(result != 0);
    test_assert(result == base);

    ecs_fini(world);
}

void Pairs_get_target_for_wildcard_from_nested_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_id_t pair = ecs_pair(Rel, Obj);
    ecs_entity_t base = ecs_new_w_id(world, pair);
    ecs_entity_t base_2 = ecs_new_w_pair(world, EcsIsA, base);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base_2);
    

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, 
        ecs_pair(Rel, EcsWildcard));
    test_assert(result != 0);
    test_assert(result == base);

    ecs_fini(world);
}

void Pairs_ignore_childof_from_base(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_entity_t parent = ecs_new_id(world);
    ecs_entity_t base = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, 
        ecs_pair(EcsChildOf, parent));
    test_assert(result == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_empty(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, Tag);
    test_assert(result == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_empty_no_rel(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_id(world);
    ecs_entity_t result = ecs_get_target_for_id(world, e, 0, Tag);
    test_assert(result == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_id_not_empty_not_found(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e = ecs_new(world, TagA);
    ecs_entity_t result = ecs_get_target_for_id(world, e, 0, TagB);
    test_assert(result == 0);

    ecs_fini(world);
}

void Pairs_get_target_for_id_from_stage(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);

    ecs_world_t *stage = ecs_get_stage(world, 0);
    ecs_readonly_begin(world);
    ecs_entity_t result = ecs_get_target_for_id(stage, e, EcsIsA, Tag);
    test_assert(result != 0);
    test_assert(result == base);
    ecs_readonly_end(world);

    ecs_fini(world);
}

void Pairs_get_target_for_id_no_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t base = ecs_new(world, Tag);
    ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base);
    ecs_add_id(world, e, Tag);

    ecs_entity_t result = ecs_get_target_for_id(world, e, EcsIsA, 0);
    test_assert(result != 0);
    test_assert(result == base);

    ecs_fini(world);
}

void Pairs_add_exclusive_relation_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e = ecs_new_id(world);

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));

    ecs_add_pair(world, e, Rel, ObjB);
    test_assert( ecs_has_pair(world, e, Rel, ObjB));
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_add_same_exclusive_relation_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);

    ecs_entity_t e = ecs_new_id(world);

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_set_exclusive_relation_twice(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Start);
    ECS_TAG(world, Stop);

    ecs_add_id(world, ecs_id(Position), EcsExclusive);

    ecs_entity_t e = ecs_new_id(world);

    ecs_set_pair(world, e, Position, Start, {10, 20});
    test_assert( ecs_has_pair(world, e, ecs_id(Position), Start));

    ecs_set_pair(world, e, Position, Stop, {20, 30});
    test_assert( ecs_has_pair(world, e, ecs_id(Position), Stop));
    test_assert( !ecs_has_pair(world, e, ecs_id(Position), Start));

    const Position *p = ecs_get_pair(world, e, Position, Stop);
    test_assert(p != NULL);
    test_int(p->x, 20);
    test_int(p->y, 30);

    p = ecs_get_pair(world, e, Position, Start);
    test_assert(p == NULL);

    ecs_fini(world);
}

void Pairs_add_exclusive_non_empty_table(void) {
    ecs_world_t *world = ecs_mini();    

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_TAG(world, TagC);
    ECS_TAG(world, TagD);

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagA);
    ecs_add(world, e, TagB);
    ecs_add(world, e, TagC);
    ecs_add(world, e, TagD);

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));

    ecs_add_pair(world, e, Rel, ObjB);
    test_assert( ecs_has_pair(world, e, Rel, ObjB));
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_add_exclusive_non_empty_table_w_pairs(void) {
    ecs_world_t *world = ecs_mini();    

    ECS_TAG(world, RelA);

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ECS_TAG(world, RelB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, RelA, ObjA);
    ecs_add_pair(world, e, RelB, ObjA);

    ecs_add_pair(world, e, Rel, ObjA);
    test_assert( ecs_has_pair(world, e, Rel, ObjA));
    
    ecs_add_pair(world, e, Rel, ObjB);
    test_assert( ecs_has_pair(world, e, Rel, ObjB));
    test_assert( !ecs_has_pair(world, e, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_add_pair_to_entity_w_exclusive_pair(void) {
    ecs_world_t *world = ecs_mini();    

    ECS_TAG(world, ObjA);

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, EcsChildOf, ObjA);
    test_assert( ecs_has_pair(world, e, EcsChildOf, ObjA));

    ecs_add_pair(world, e, RelA, ObjA);
    test_assert( ecs_has_pair(world, e, RelA, ObjA));

    ecs_add_pair(world, e, RelB, ObjA);
    test_assert( ecs_has_pair(world, e, RelB, ObjA));

    ecs_fini(world);
}

void Pairs_add_pair_to_entity_w_scope(void) {
    ecs_world_t *world = ecs_mini();    

    ECS_TAG(world, ObjA);

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);

    ecs_set_scope(world, ObjA);

    ecs_entity_t e = ecs_new(world, 0);

    ecs_add_pair(world, e, RelA, ObjA);
    test_assert( ecs_has_pair(world, e, RelA, ObjA));

    ecs_add_pair(world, e, RelB, ObjA);
    test_assert( ecs_has_pair(world, e, RelB, ObjA));

    ecs_set_scope(world, 0);

    ecs_fini(world);
}

void Pairs_add_existing_exclusive_pair_after_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_TAG(world, ObjA);

    ecs_entity_t parent = ecs_new_id(world);

    ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, parent);
    ecs_add_pair(world, e, EcsChildOf, ObjA);
    test_assert( ecs_has_pair(world, e, EcsChildOf, ObjA));

    ecs_add_pair(world, e, RelA, ObjA);
    test_assert( ecs_has_pair(world, e, EcsChildOf, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjA));

    ecs_add_pair(world, e, EcsChildOf, ObjA);
    test_assert( ecs_has_pair(world, e, EcsChildOf, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjA));

    ecs_add_pair(world, e, RelB, ObjA);

    test_assert( ecs_has_pair(world, e, EcsChildOf, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjA));

    ecs_fini(world);
}

void Pairs_add_remove_exclusive_property(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel = ecs_new_w_id(world, EcsExclusive);
    ecs_remove_id(world, rel, EcsExclusive);

    ecs_entity_t tgt_1 = ecs_new_id(world);
    ecs_entity_t tgt_2 = ecs_new_id(world);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, rel, tgt_1);
    test_assert( ecs_has_pair(world, e, rel, tgt_1));

    ecs_add_pair(world, e, rel, tgt_2);
    test_assert( ecs_has_pair(world, e, rel, tgt_2));
    test_assert( ecs_has_pair(world, e, rel, tgt_1));

    ecs_fini(world);
}

void Pairs_add_symmetric_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_add_id(world, Rel, EcsSymmetric);

    ecs_add_pair(world, ObjA, Rel, ObjB);
    test_assert(ecs_has_pair(world, ObjA, Rel, ObjB));
    test_assert(ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_remove_symmetric_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_add_id(world, Rel, EcsSymmetric);

    ecs_add_pair(world, ObjA, Rel, ObjB);
    test_assert(ecs_has_pair(world, ObjA, Rel, ObjB));
    test_assert(ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_remove_pair(world, ObjA, Rel, ObjB);
    test_assert(!ecs_has_pair(world, ObjA, Rel, ObjB));
    test_assert(!ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_delete_entity_w_symmetric_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_add_id(world, Rel, EcsSymmetric);

    ecs_add_pair(world, ObjA, Rel, ObjB);
    test_assert(ecs_has_pair(world, ObjA, Rel, ObjB));
    test_assert(ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_delete(world, ObjA);

    test_assert(!ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_add_symmetric_exclusive_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);
    ECS_TAG(world, ObjC);

    ecs_add_id(world, Rel, EcsSymmetric);
    ecs_add_id(world, Rel, EcsExclusive);

    ecs_add_pair(world, ObjA, Rel, ObjB);
    test_assert(ecs_has_pair(world, ObjA, Rel, ObjB));
    test_assert(ecs_has_pair(world, ObjB, Rel, ObjA));

    ecs_add_pair(world, ObjA, Rel, ObjC);
    test_assert(ecs_has_pair(world, ObjA, Rel, ObjC));
    test_assert(!ecs_has_pair(world, ObjB, Rel, ObjA));
    test_assert(ecs_has_pair(world, ObjC, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_add_symmetric_recycled_relation(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t obj_a = ecs_new_id(world);
    ecs_entity_t obj_b = ecs_new_id(world);
    ecs_delete(world, rel);
    rel = ecs_new_id(world);
    ecs_delete(world, obj_a);
    obj_a = ecs_new_id(world);
    ecs_delete(world, obj_b);
    obj_b = ecs_new_id(world);

    test_assert(rel != (uint32_t)rel);
    test_assert(obj_a != (uint32_t)obj_a);
    test_assert(obj_b != (uint32_t)obj_b);

    ecs_add_id(world, rel, EcsSymmetric);

    ecs_add_pair(world, obj_a, rel, obj_b);

    test_assert(ecs_has_pair(world, obj_a, rel, obj_b));
    test_assert(ecs_has_pair(world, obj_b, rel, obj_a));

    ecs_fini(world);
}

void Pairs_with(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_ENTITY(world, TagB, (With, TagA));

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagB);
    test_assert( ecs_has(world, e, TagA));
    test_assert( ecs_has(world, e, TagB));

    ecs_fini(world);
}

void Pairs_2_with(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);
    ECS_ENTITY(world, TagC, (With, TagA), (With, TagB));

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagC);
    test_assert( ecs_has(world, e, TagA));
    test_assert( ecs_has(world, e, TagB));
    test_assert( ecs_has(world, e, TagC));

    ecs_fini(world);
}

void Pairs_nested_with(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_ENTITY(world, TagB, (With, TagA));
    ECS_ENTITY(world, TagC, (With, TagB));

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagC);
    test_assert( ecs_has(world, e, TagA));
    test_assert( ecs_has(world, e, TagB));
    test_assert( ecs_has(world, e, TagC));

    ecs_fini(world);
}

void Pairs_with_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Obj);
    ECS_TAG(world, RelA);
    ECS_ENTITY(world, RelB, (With, RelA));

    ecs_entity_t e = ecs_new_w_pair(world, RelB, Obj);
    test_assert( ecs_has_pair(world, e, RelA, Obj));
    test_assert( ecs_has_pair(world, e, RelB, Obj));

    ecs_fini(world);
}

void Pairs_2_with_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Obj);
    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_ENTITY(world, RelC, (With, RelA), (With, RelB));

    ecs_entity_t e = ecs_new_w_pair(world, RelC, Obj);
    test_assert( ecs_has_pair(world, e, RelA, Obj));
    test_assert( ecs_has_pair(world, e, RelB, Obj));

    ecs_fini(world);
}

void Pairs_nested_with_relation(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Obj);
    ECS_TAG(world, RelA);
    ECS_ENTITY(world, RelB, (With, RelA));
    ECS_ENTITY(world, RelC, (With, RelB));

    ecs_entity_t e = ecs_new_w_pair(world, RelC, Obj);
    test_assert( ecs_has_pair(world, e, RelA, Obj));
    test_assert( ecs_has_pair(world, e, RelB, Obj));
    test_assert( ecs_has_pair(world, e, RelC, Obj));

    ecs_fini(world);
}

void Pairs_with_for_existing(void) {
    install_test_abort();
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add(world, e, TagB);
    test_assert( !ecs_has(world, e, TagA));
    test_assert( ecs_has(world, e, TagB));
    
    test_expect_abort();
    ecs_add_pair(world, TagB, EcsWith, TagA);
}

void Pairs_65k_relations(void) {
    ecs_world_t *world = ecs_mini();

    ecs_set_entity_range(world, 65536, 0);

    ecs_entity_t rel = ecs_new_id(world);
    ecs_entity_t obj = ecs_new_id(world);
    ecs_entity_t e = ecs_new_w_pair(world, rel, obj);
    test_assert(ecs_has_pair(world, e, rel, obj));

    ecs_fini(world);
}

void Pairs_remove_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, RelA, ObjA);
    ecs_add_pair(world, e, RelA, ObjB);
    ecs_add_pair(world, e, RelB, ObjA);
    ecs_add_pair(world, e, RelB, ObjB);

    test_assert( ecs_has_pair(world, e, RelA, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjB));
    test_assert( ecs_has_pair(world, e, RelB, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjB));

    ecs_remove_pair(world, e, RelA, EcsWildcard);

    test_assert( !ecs_has_pair(world, e, RelA, ObjA));
    test_assert( !ecs_has_pair(world, e, RelA, ObjB));
    test_assert( ecs_has_pair(world, e, RelB, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjB));
    
    ecs_fini(world);
}

void Pairs_remove_relation_wildcard(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, RelA, ObjA);
    ecs_add_pair(world, e, RelA, ObjB);
    ecs_add_pair(world, e, RelB, ObjA);
    ecs_add_pair(world, e, RelB, ObjB);

    test_assert( ecs_has_pair(world, e, RelA, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjB));
    test_assert( ecs_has_pair(world, e, RelB, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjB));

    ecs_remove_pair(world, e, EcsWildcard, ObjA);
    
    test_assert( !ecs_has_pair(world, e, RelA, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjB));
    test_assert( !ecs_has_pair(world, e, RelB, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjB));

    ecs_fini(world);
}

void Pairs_add_not_alive_relation(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t obj = ecs_new_id(world);
    ecs_entity_t e = ecs_new_w_pair(world, 2000, obj);
    test_assert(ecs_has_pair(world, e, 2000, obj));

    ecs_fini(world);
}

void Pairs_remove_wildcard_all(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, RelA);
    ECS_TAG(world, RelB);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, RelA, ObjA);
    ecs_add_pair(world, e, RelA, ObjB);
    ecs_add_pair(world, e, RelB, ObjA);
    ecs_add_pair(world, e, RelB, ObjB);

    test_assert( ecs_has_pair(world, e, RelA, ObjA));
    test_assert( ecs_has_pair(world, e, RelA, ObjB));
    test_assert( ecs_has_pair(world, e, RelB, ObjA));
    test_assert( ecs_has_pair(world, e, RelB, ObjB));

    ecs_remove_pair(world, e, EcsWildcard, EcsWildcard);
    
    test_assert( !ecs_has_pair(world, e, RelA, ObjA));
    test_assert( !ecs_has_pair(world, e, RelA, ObjB));
    test_assert( !ecs_has_pair(world, e, RelB, ObjA));
    test_assert( !ecs_has_pair(world, e, RelB, ObjB));

    ecs_fini(world);
}

void Pairs_inherit_exclusive(void) {    
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, Exclusive);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t base = ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_assert( ecs_has_pair(world, inst, Rel, ObjA));

    ecs_add_pair(world, inst, Rel, ObjB);

    test_assert( ecs_has_pair(world, inst, Rel, ObjB));
    test_assert( !ecs_has_pair(world, inst, Rel, ObjA));

    ecs_fini(world);
}

void Pairs_dont_inherit(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, TagA);
    ecs_add_id(world, TagA, EcsDontInherit);
    ECS_TAG(world, TagB);

    ecs_entity_t base = ecs_new_w_id(world, TagA);
    ecs_add(world, base, TagB);
    ecs_entity_t inst = ecs_new_w_pair(world, EcsIsA, base);

    test_assert( ecs_has(world, base, TagA));
    test_assert( !ecs_has(world, inst, TagA));

    test_assert( ecs_has(world, base, TagB));
    test_assert( ecs_has(world, inst, TagB));

    ecs_fini(world);
}

void Pairs_has_wildcard_w_pair(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_entity_t e = ecs_new_w_pair(world, Rel, Obj);

    test_bool(ecs_has_pair(world, e, EcsWildcard, EcsWildcard), true);
    test_bool(ecs_has_pair(world, e, Rel, EcsWildcard), true);
    test_bool(ecs_has_pair(world, e, EcsWildcard, Obj), true);
    test_bool(ecs_has_pair(world, e, Rel, Obj), true);
    test_bool(ecs_has_id(world, e, EcsWildcard), false);

    ecs_fini(world);
}

void Pairs_has_pair_wildcard_w_tag(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new(world, Tag);

    test_bool(ecs_has_pair(world, e, EcsWildcard, EcsWildcard), false);
    test_bool(ecs_has_id(world, e, Tag), true);
    test_bool(ecs_has_id(world, e, EcsWildcard), true);

    ecs_fini(world);
}

void Pairs_oneof_self(void) {
    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, OneOf);
    ECS_ENTITY(world, ObjA, (ChildOf, Rel));
    ECS_ENTITY(world, ObjB, (ChildOf, Rel));

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, Rel, ObjA);
    test_assert(ecs_has_pair(world, e, Rel, ObjA));

    ecs_add_pair(world, e, Rel, ObjB);
    test_assert(ecs_has_pair(world, e, Rel, ObjB));

    ecs_fini(world);
}

void Pairs_oneof_other(void) {
    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Parent);
    ECS_ENTITY(world, Rel, (OneOf, Parent));
    ECS_ENTITY(world, ObjA, (ChildOf, Parent));
    ECS_ENTITY(world, ObjB, (ChildOf, Parent));

    ecs_entity_t e = ecs_new_id(world);
    ecs_add_pair(world, e, Rel, ObjA);
    test_assert(ecs_has_pair(world, e, Rel, ObjA));

    ecs_add_pair(world, e, Rel, ObjB);
    test_assert(ecs_has_pair(world, e, Rel, ObjB));

    ecs_fini(world);
}

void Pairs_oneof_self_constraint_violated(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_ENTITY(world, Rel, OneOf);
    ECS_ENTITY(world, ObjA, (ChildOf, Rel));
    ECS_ENTITY(world, ObjB, (ChildOf, Rel));
    ECS_TAG(world, ObjC);

    ecs_entity_t e = ecs_new_id(world);

    test_expect_abort();
    ecs_add_pair(world, e, Rel, ObjC);
}

void Pairs_oneof_other_constraint_violated(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Parent);
    ECS_ENTITY(world, Rel, (OneOf, Parent));
    ECS_ENTITY(world, ObjA, (ChildOf, Parent));
    ECS_ENTITY(world, ObjB, (ChildOf, Parent));
    ECS_TAG(world, ObjC);

    ecs_entity_t e = ecs_new_id(world);

    test_expect_abort();
    ecs_add_pair(world, e, Rel, ObjC);
}

void Pairs_oneof_other_rel_parent_constraint_violated(void) {
    install_test_abort();

    ecs_world_t *world = ecs_mini();

    ECS_TAG(world, Parent);
    ECS_ENTITY(world, Rel, (OneOf, Parent));
    ECS_ENTITY(world, ObjA, (ChildOf, Parent));
    ECS_ENTITY(world, ObjB, (ChildOf, Parent));
    ECS_ENTITY(world, ObjC, (ChildOf, Rel));

    ecs_entity_t e = ecs_new_id(world);

    test_expect_abort();
    ecs_add_pair(world, e, Rel, ObjC);
}
