import { before, beforeEach, describe, it } from 'node:test' import { addComponent, addEntity, createWorld, defineComponent, resetWorld, Types } from 'bitecs' import assert from './assert.js' import { query } from '../src/query.js' let engine = {} const create = (world, ...components) => { const entity = addEntity(world) components.forEach(cmp => addComponent(world, cmp, entity)) return entity } const relate = (world, from, type, ...b) => { return b.map(to => { const edge = addEntity(world) addComponent(world, type, edge) update(type, { from, to }, edge) return edge }) } const update = (type, values, entity) => { Object.entries(values).forEach(([k, v]) => { type[k][entity] = v }) } const EdgeDef = { from: Types.eid, to: Types.eid } const defineEdge = (schema, max) => defineComponent({ ...EdgeDef, ...schema }, max) describe('query', () => { before(() => { const world = { default: createWorld() } const component = { 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) } engine = { world, component } }) beforeEach(() => { Object.values(engine.world).forEach(world => resetWorld(world)) }) it('should query on single components', () => { const world = engine.world.default const { Player, NPC, Knows } = engine.component const player = create(world, Player) // 0 const a = create(world, NPC) // 1 const b = create(world, NPC) // 2 const c = create(world, NPC) // 3 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 // tag components should return an empty object assert.deepEqual( query('MATCH (player:Player) RETURN player', engine).unwrap(), [{ player: {} }] ) assert.deepEqual( query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(), [ { player: {}, e1: { from: 0, to: 1 }, a: {} }, { player: {}, e1: { from: 0, to: 3 }, a: {} } ] ) assert.deepEqual( query('MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b', engine).unwrap(), [ { player: {}, e1: { from: 0, to: 1 }, a: {}, e2: { from: 1, to: 2 }, b: {} }, { player: {}, e1: { from: 0, to: 1 }, a: {}, e2: { from: 1, to: 3 }, b: {} } ] ) }) it('should match multiple components', () => { const world = engine.world.default const { Player, Health } = engine.component const player = create(world, Player, Health) Health.max[player] = 50 Health.current[player] = 25 // an unspecified component should return an Entity assert.deepEqual( query('MATCH (e, h:Health) RETURN e, h.max', engine).unwrap(), [{ e: 11, h: { max: 50 } }] ) assert.deepEqual( query('MATCH (e:Entity, h:Health) RETURN e, h.max', engine).unwrap(), [{ e: 11, h: { max: 50 } }] ) }) it('should support filtering', () => { const world = engine.world.default const { Player, Health, Damaged } = engine.component const player = create(world, Player, Health) // 12 const enemy = create(world, Health) // 13 update(Health, { max: 50, current: 25 }, player) update(Health, { max: 50, current: 35 }, enemy) const [edge] = relate(world, player, Damaged, enemy) update(Damaged, { damage: 10 }, edge) // assert.deepEqual( // query('MATCH (e, h:Health) WHERE h.current < 30 RETURN e, h.max', engine).unwrap(), // [{ e: 12, h: { max: 50 } }] // ) // assert.deepEqual( // query('MATCH (e, h:Health) WHERE e = 13 AND h.max = 50 OR h.current < 25 RETURN e, h.max', engine).unwrap(), // [{ e: 13, h: { max: 50 } }] // ) // assert.deepEqual( // query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 RETURN e, h.max AS maxHealth', engine).unwrap(), // [ // { e: 12, maxHealth: 50 }, // { e: 13, maxHealth: 50 } // ] // ) // 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(), // [{ e: 12, maxHealth: 50 }] // ) // assert.deepEqual( // query('MATCH (:Player)-[d:Damaged]->(h:Health) RETURN h.current AS health, d.damage AS damage', engine).unwrap(), // [{ damage: 10, health: 35 }] // ) update(Damaged, { damage: 50 }, edge) assert.deepEqual( query('MATCH (n)-[d:Damaged]->(e, h:Health) WHERE d.damage > h.current AND h.current > 0 RETURN e', engine).unwrap(), [{ e: 13 }] ) }) })