Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
[IR-2053] Unit Tests: Core All ECS Hooks/Reactors (#10185)
Browse files Browse the repository at this point in the history
* fix: Typo in `SystemFunctions.test.ts`

* fix: Typo in `ComponentFunctions.test.ts`

* tst: Skeleton for the `useComponent` test cases

* chg: `useAllComponents` initializes the result with `getAllComponents`

* tst: UnitTests for `ComponentFunctions.ts` hooks

* tst: UnitTests for `UUIDComponent.ts` (phase 0)

* tst: UnitTests for `UUIDComponent.ts` (phase 1)

* tst: Small change to the `useEntityByUUID` hook test

* tst: Extra check for the `getEntityByUUID` test

* tst: Small formatting change to one of the assertion messages

* tst: `npm run test` vscode runner for the `ecs` package

* tst: UnitTests for `QueryFunctions.ts`

* fix: Error in `useEntityByUUID` tests

* tst: Apply review suggestions

* tst: Replace instances of `InitialValue` with `undefined & counter`

As per review:
- Leftover console.log calls removed
- InitialValue checks swapped with `undefined` and a `counter` variable

For Types readability:
- Change type casts to use a `ResultType` that unions undefined with the expected returned type

* fix: Typescript type conflicts

* tst: Remove `useAllComponents` tests. Hook removed at #a1a6682a

* tst: Remove `QueryFunctions.test.tsx` for merge conflict resolution

* tst: Add UnitTests to `QueryFunctions.test.tsx` after merge conflict
  • Loading branch information
heysokam authored Jun 5, 2024
1 parent b5c1d4c commit c62fd21
Show file tree
Hide file tree
Showing 5 changed files with 575 additions and 9 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
}
],
"configurations": [

{
"command": "cd packages/ui && npm run test:watch",
"name": "ui-jest",
Expand Down Expand Up @@ -96,6 +97,12 @@
"request": "launch",
"type": "node-terminal",
},
{
"command": "cd packages/ecs && npm run test",
"name": "npm run test - ecs",
"request": "launch",
"type": "node-terminal",
},
{
"command": "cd packages/engine && npm run test",
"name": "npm run test - engine",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/

import { act, render } from '@testing-library/react'
import assert from 'assert'
import { Types } from 'bitecs'
import React, { useEffect } from 'react'

import {
ComponentMap,
Expand All @@ -33,10 +35,14 @@ import {
getComponent,
hasComponent,
removeComponent,
setComponent
setComponent,
useComponent,
useOptionalComponent
} from './ComponentFunctions'
import { destroyEngine, startEngine } from './Engine'
import { createEntity } from './EntityFunctions'
import { Entity, EntityUUID, UndefinedEntity } from './Entity'
import { createEntity, removeEntity } from './EntityFunctions'
import { UUIDComponent } from './UUIDComponent'

describe('ComponentFunctions', async () => {
beforeEach(() => {
Expand Down Expand Up @@ -219,7 +225,7 @@ describe('ComponentFunctions', async () => {
})

describe('removeComponent', () => {
it('should have component', () => {
it('should remove component', () => {
const TestComponent = defineComponent({ name: 'TestComponent', onInit: () => true })

const entity = createEntity()
Expand All @@ -233,7 +239,7 @@ describe('ComponentFunctions', async () => {
assert.ok(TestComponent.stateMap[entity]!.promised === true)
})

it('should have component with AoS values', () => {
it('should remove component with AoS values', () => {
const TestComponent = defineComponent({
name: 'TestComponent',

Expand All @@ -257,7 +263,7 @@ describe('ComponentFunctions', async () => {
assert.ok(!hasComponent(entity, TestComponent))
})

it('should have component with SoA values', () => {
it('should remove component with SoA values', () => {
const { f32 } = Types
const ValueSchema = { value: f32 }
const TestComponent = defineComponent({ name: 'TestComponent', schema: ValueSchema })
Expand Down Expand Up @@ -291,6 +297,190 @@ describe('ComponentFunctions', async () => {
assert.ok(component3)
})
})
})

describe('ComponentFunctions Hooks', async () => {
describe('useComponent', async () => {
type ResultType = undefined | string
const ResultValue: ResultType = 'ReturnValue'
const component = defineComponent({ name: 'TestComponent', onInit: () => ResultValue })
let testEntity = UndefinedEntity
let result = undefined as ResultType
let counter = 0

beforeEach(() => {
startEngine()
ComponentMap.clear()
testEntity = createEntity()
})

afterEach(() => {
counter = 0
removeEntity(testEntity)
return destroyEngine()
})

// Define the Reactor that will run the tested hook
const Reactor = () => {
const data = useComponent(testEntity, component)
useEffect(() => {
result = data.value as ResultType
++counter
}, [data])
return null
}

it('assigns the correct value with onInit', async () => {
setComponent(testEntity, component)
assert.equal(counter, 0, "The reactor shouldn't have run before rendering")
const tag = <Reactor />
const { rerender, unmount } = render(tag)
await act(() => rerender(tag))
assert.equal(counter, 1, `The reactor has run an incorrect number of times: ${counter}`)
assert.notEqual(result, undefined, "The result data didn't get assigned.")
assert.equal(result, ResultValue, `Did not return the correct data. result = ${result}`)
unmount()
})
}) // useComponent

describe('useOptionalComponent : Simple cases', async () => {
type ResultType = string | undefined
const ResultValue: ResultType = 'ReturnValue'
const component = defineComponent({ name: 'TestComponent', onInit: () => ResultValue })
let testEntity = UndefinedEntity
let result: ResultType = undefined
let counter = 0

beforeEach(() => {
startEngine()
ComponentMap.clear()
testEntity = createEntity()
})

afterEach(() => {
counter = 0
removeEntity(testEntity)
return destroyEngine()
})

// Define the Reactor that will run the tested hook
const Reactor = () => {
const data = useOptionalComponent(testEntity, component)
useEffect(() => {
result = data?.value
++counter
}, [data])
return null
}

it("returns undefined when the component wasn't set yet", async () => {
assert.equal(counter, 0, "The reactor shouldn't have run before rendering")
const tag = <Reactor />
const { rerender, unmount } = render(tag)
await act(() => rerender(tag))
assert.equal(counter, 1, `The reactor has run an incorrect number of times: ${counter}`)
assert.equal(result, undefined, `Should have returned undefined.`)
unmount()
})

it('returns the correct data when the component has been set', async () => {
assert.equal(counter, 0, "The reactor shouldn't have run before rendering")
const tag = <Reactor />
const { rerender, unmount } = render(tag)
setComponent(testEntity, component)
await act(() => rerender(tag))
assert.equal(true, hasComponent(testEntity, component), 'The test entity did not get its component set correctly')
assert.notEqual(result, undefined, "The result data didn't get assigned.")
assert.equal(counter, 2, `The reactor has run an incorrect number of times: ${counter}`)
assert.equal(result, ResultValue, `Did not return the correct data.`)
unmount()
})
}) // useOptionalComponent : Simple Cases

describe('useOptionalComponent : Isolated Test Cases', async () => {
/** @note These test cases are isolated from each other, by defining everything without using any common code (like beforeEach/afterEach/etc) */

it('returns different data when the entity is changed', async () => {
// Initialize the isolated case
startEngine()
ComponentMap.clear()

// Initialize the dummy data
type ResultType = EntityUUID | undefined
const component = UUIDComponent
const TestUUID1 = 'TestUUID1' as EntityUUID
const TestUUID2 = 'TestUUID2' as EntityUUID
const oneEntity = createEntity()
const twoEntity = createEntity()
let result: ResultType = undefined
let counter = 0

setComponent(oneEntity, UUIDComponent, TestUUID1)
setComponent(twoEntity, UUIDComponent, TestUUID2)

// Define the Reactor that will run the tested hook
const Reactor = (props: { entity: Entity }) => {
// Call the hook to set the data
const data = useComponent(props.entity, component)
useEffect(() => {
result = data.value
++counter
}, [data])
return null
}

// Run the test case
assert.equal(counter, 0, "The reactor shouldn't have run before rendering")
const tag = <Reactor entity={oneEntity} />
const { rerender, unmount } = render(tag)
await act(() => rerender(tag))
assert.equal(counter, 1, `The reactor has run an incorrect number of times: ${counter}`)
assert.notEqual(result, undefined, "The result data didn't get initialized")
assert.equal(result, TestUUID1)
await act(() => rerender(<Reactor entity={twoEntity} />))
assert.equal(result, TestUUID2)

// Terminate the Reactor and Isolated Test
unmount()
return destroyEngine()
})

it('suspense should work', async () => {
// Initialize the isolated case
startEngine()
ComponentMap.clear()

// Initialize the dummy data
const entity = createEntity()
const TestComponent = defineComponent({ name: 'TestComponent' })
let result = 0

// Define the Reactor that will run the tested hook
const Reactor = () => {
result++
const data = useComponent(entity, TestComponent)
result++
useEffect(() => {
result++
}, [data])
return null
}

// Run the test case
const tag = <Reactor />
assert.equal(TestComponent.stateMap[entity]!, undefined)
const { rerender, unmount } = render(tag)
assert.equal(result, 1)

setComponent(entity, TestComponent)
await act(() => rerender(tag))
assert.equal(result, 4)

// Terminate the Reactor and Isolated Test
unmount()
return destroyEngine()
})
}) // useOptionalComponent : Isolated Test Cases

// TODO
describe('defineQuery', () => {})
Expand Down
Loading

0 comments on commit c62fd21

Please sign in to comment.