graph-ecs/tests/query.test.js

185 lines
6.3 KiB
JavaScript
Raw Normal View History

2024-11-25 18:00:48 +01:00
import { before, beforeEach, describe, it } from 'node:test'
import { addComponent, addEntity, createWorld, defineComponent, resetWorld, Types } from 'bitecs'
2024-11-20 10:54:35 +01:00
import assert from './assert.js'
import { query } from '../src/query.js'
let engine = {}
2024-11-25 05:27:08 +01:00
const create = (world, ...components) => {
const entity = addEntity(world)
components.forEach(cmp => addComponent(world, cmp, entity))
return entity
}
2024-11-28 04:20:43 +01:00
const relate = (world, from, type, ...b) => {
return b.map(to => {
2024-11-25 05:27:08 +01:00
const edge = addEntity(world)
addComponent(world, type, edge)
2024-11-28 04:20:43 +01:00
update(type, { from, to }, edge)
2024-11-25 05:27:08 +01:00
return edge
})
}
2024-11-28 04:20:43 +01:00
const update = (type, values, entity) => {
Object.entries(values).forEach(([k, v]) => { type[k][entity] = v })
}
2024-11-28 09:08:37 +01:00
const EdgeDef = { from: Types.eid, to: Types.eid }
const defineEdge = (schema, max) => defineComponent({ ...EdgeDef, ...schema }, max)
2024-11-20 10:54:35 +01:00
describe('query', () => {
2024-11-25 09:34:24 +01:00
before(() => {
2024-11-20 10:54:35 +01:00
const world = { default: createWorld() }
const component = {
2024-11-28 09:08:37 +01:00
Player: defineComponent(null, 20),
NPC: defineComponent(null, 20),
Health: defineComponent({ current: Types.ui8, max: Types.ui8 }, 20),
Knows: defineComponent(EdgeDef, 20),
Damaged: defineEdge({
damage: Types.ui16
}, 20)
2024-11-20 10:54:35 +01:00
}
engine = { world, component }
})
2024-11-25 18:00:48 +01:00
beforeEach(() => {
Object.values(engine.world).forEach(world => resetWorld(world))
})
it('should query on single components', () => {
2024-11-25 05:27:08 +01:00
const world = engine.world.default
2024-11-20 10:54:35 +01:00
const { Player, NPC, Knows } = engine.component
2024-11-25 05:27:08 +01:00
const player = create(world, Player) // 0
const a = create(world, NPC) // 1
const b = create(world, NPC) // 2
const c = create(world, NPC) // 3
2024-11-20 10:54:35 +01:00
2024-11-25 05:27:08 +01:00
relate(world, player, Knows, a, c) // 4, 5
relate(world, a, Knows, player, b, c) // 6, 7, 8
relate(world, b, Knows, a) // 9
relate(world, c, Knows, player) // 10
2024-11-20 10:54:35 +01:00
2024-11-28 04:20:43 +01:00
// tag components should return an empty object
assert.deepEqual(
query('MATCH (player:Player) RETURN player', engine).unwrap(),
[{ player: {} }]
)
2024-11-22 11:46:56 +01:00
assert.deepEqual(
query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(),
[
2024-11-30 11:53:00 +01:00
{ player: {}, e1: { from: player, to: a }, a: {} },
{ player: {}, e1: { from: player, to: c }, a: {} }
]
)
2024-11-22 11:46:56 +01:00
assert.deepEqual(
query('MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b', engine).unwrap(),
[
2024-11-30 11:53:00 +01:00
{ player: {}, e1: { from: player, to: a }, a: {}, e2: { from: a, to: b }, b: {} },
{ player: {}, e1: { from: player, to: a }, a: {}, e2: { from: a, to: c }, b: {} }
2024-11-22 11:46:56 +01:00
]
)
2024-11-20 10:54:35 +01:00
})
2024-11-25 05:27:08 +01:00
it('should match multiple components', () => {
const world = engine.world.default
const { Player, Health } = engine.component
const player = create(world, Player, Health)
2024-11-25 05:27:08 +01:00
Health.max[player] = 50
Health.current[player] = 25
2024-11-25 18:00:48 +01:00
// an unspecified component should return an Entity
assert.deepEqual(
2024-11-28 04:20:43 +01:00
query('MATCH (e, h:Health) RETURN e, h.max', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: player, h: { max: 50 } }]
2024-11-25 18:00:48 +01:00
)
assert.deepEqual(
2024-11-28 04:20:43 +01:00
query('MATCH (e:Entity, h:Health) RETURN e, h.max', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: player, h: { max: 50 } }]
2024-11-26 22:38:27 +01:00
)
})
it('should support filtering', () => {
const world = engine.world.default
2024-11-28 09:08:37 +01:00
const { Player, Health, Damaged } = engine.component
2024-11-28 04:20:43 +01:00
const player = create(world, Player, Health) // 12
const enemy = create(world, Health) // 13
2024-11-26 22:38:27 +01:00
2024-11-28 04:20:43 +01:00
update(Health, { max: 50, current: 25 }, player)
update(Health, { max: 50, current: 35 }, enemy)
2024-11-30 11:53:00 +01:00
const [edge] = relate(world, player, Damaged, enemy) // 14
2024-11-28 09:08:37 +01:00
update(Damaged, { damage: 10 }, edge)
2024-11-26 22:38:27 +01:00
2024-11-29 09:27:39 +01:00
assert.deepEqual(
query('MATCH (e, h:Health) WHERE h.current < 30 RETURN e, h.max', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: player, h: { max: 50 } }]
2024-11-29 09:27:39 +01:00
)
assert.deepEqual(
query('MATCH (e, h:Health) WHERE e = 13 AND h.max = 50 OR h.current < 25 RETURN e, h.max', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: enemy, h: { max: 50 } }]
2024-11-29 09:27:39 +01:00
)
assert.deepEqual(
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 RETURN e, h.max AS maxHealth', engine).unwrap(),
[
2024-11-30 11:53:00 +01:00
{ e: player, maxHealth: 50 },
{ e: enemy, maxHealth: 50 }
2024-11-29 09:27:39 +01:00
]
)
assert.deepEqual(
query('MATCH (e, :Player, h:Health) WHERE (h.max = 50 OR h.current > 45) AND e = 12 RETURN e, h.max AS maxHealth', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: player, maxHealth: 50 }]
2024-11-29 09:27:39 +01:00
)
assert.deepEqual(
query('MATCH (:Player)-[d:Damaged]->(h:Health) RETURN h.current AS health, d.damage AS damage', engine).unwrap(),
[{ damage: 10, health: 35 }]
)
2024-11-28 10:19:58 +01:00
update(Damaged, { damage: 50 }, edge)
2024-11-26 22:38:27 +01:00
assert.deepEqual(
2024-11-28 10:19:58 +01:00
query('MATCH (n)-[d:Damaged]->(e, h:Health) WHERE d.damage > h.current AND h.current > 0 RETURN e', engine).unwrap(),
2024-11-30 11:53:00 +01:00
[{ e: enemy }]
)
})
it('properties should match', () => {
const world = engine.world.default
const { Player, Health, Damaged } = engine.component
const player = create(world, Player, Health)
const enemy = create(world, Health)
update(Health, { max: 50, current: 25 }, player)
update(Health, { max: 50, current: 35 }, enemy)
const [edge] = relate(world, player, Damaged, enemy) // 14
update(Damaged, { damage: 10 }, edge)
assert.deepEqual(
query('MATCH (e, h:Health { current: 35 }) RETURN e', engine).unwrap(),
[{ e: enemy }]
)
assert.deepEqual(
query('MATCH (e, h:Health { max: 50 }) RETURN e', engine).unwrap(),
[{ e: player }, { e: enemy }]
)
assert.deepEqual(
query('MATCH (e, h:Health { current: 35, max: 50 }) RETURN e', engine).unwrap(),
[{ e: enemy }]
)
assert.deepEqual(
query('MATCH (a)-[:Damaged { damage: 10 }]->(b) RETURN a, b', engine).unwrap(),
[{ a: player, b: enemy }]
)
})
2024-11-20 10:54:35 +01:00
})