Skip to content

Commit 01d3d66

Browse files
committed
feat: add tests
1 parent 2d44f3a commit 01d3d66

1 file changed

Lines changed: 244 additions & 0 deletions

File tree

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import { renderHook, act } from '@testing-library/react'
2+
import { Entity, CrdtMessageType } from '@dcl/ecs'
3+
import { useEntityComponent } from './useEntityComponent'
4+
import React from 'react'
5+
6+
let realUseContext: typeof React.useContext
7+
let useContextMock: jest.Mock
8+
9+
const mockComponents = [
10+
{ componentId: 1, componentName: 'Transform', has: jest.fn() },
11+
{ componentId: 2, componentName: 'Mesh Renderer', has: jest.fn() },
12+
{ componentId: 3, componentName: 'Visibility', has: jest.fn() }
13+
]
14+
15+
const mockSdk = {
16+
engine: {
17+
componentsIter: jest.fn().mockReturnValue(mockComponents),
18+
getComponentOrNull: jest.fn((id: number) => mockComponents.find((comp) => comp.componentId === id)),
19+
RootEntity: 0 as Entity
20+
},
21+
operations: {
22+
addComponent: jest.fn(),
23+
removeComponent: jest.fn(),
24+
updateSelectedEntity: jest.fn(),
25+
dispatch: jest.fn().mockResolvedValue(void 0)
26+
},
27+
components: {
28+
Transform: { componentId: 1, componentName: 'Transform', has: jest.fn() },
29+
MeshRenderer: { componentId: 2, componentName: 'Mesh Renderer', has: jest.fn() },
30+
VisibilityComponent: { componentId: 3, componentName: 'Visibility', has: jest.fn() }
31+
}
32+
}
33+
34+
jest.mock('./useChange', () => ({
35+
useChange: jest.fn().mockImplementation((callback: any) => {
36+
;(global as any).triggerChange = callback
37+
})
38+
}))
39+
40+
describe('useEntityComponent', () => {
41+
const testEntity = 123 as Entity
42+
43+
beforeEach(() => {
44+
realUseContext = React.useContext
45+
useContextMock = React.useContext = jest.fn().mockReturnValue({ sdk: mockSdk }) as any
46+
jest.clearAllMocks()
47+
mockSdk.engine.componentsIter.mockReturnValue(mockComponents)
48+
mockSdk.engine.getComponentOrNull.mockImplementation((id: number) =>
49+
mockComponents.find((comp) => comp.componentId === id)
50+
)
51+
})
52+
53+
afterEach(() => {
54+
React.useContext = realUseContext
55+
useContextMock.mockReset()
56+
})
57+
58+
describe('getComponents', () => {
59+
it('should return components present on entity when missing=false', () => {
60+
mockComponents[0].has.mockReturnValue(true)
61+
mockComponents[1].has.mockReturnValue(true)
62+
mockComponents[2].has.mockReturnValue(false)
63+
64+
const { result } = renderHook(() => useEntityComponent())
65+
66+
const components = result.current.getComponents(testEntity, false)
67+
68+
expect(components.size).toBe(2)
69+
expect(components.get(1)).toBe('Transform')
70+
expect(components.get(2)).toBe('Mesh Renderer')
71+
expect(components.has(3)).toBe(false)
72+
})
73+
74+
it('should return missing components when missing=true', () => {
75+
mockComponents[0].has.mockReturnValue(true)
76+
mockComponents[1].has.mockReturnValue(false)
77+
mockComponents[2].has.mockReturnValue(false)
78+
79+
const { result } = renderHook(() => useEntityComponent())
80+
81+
const components = result.current.getComponents(testEntity, true)
82+
83+
expect(components.size).toBe(2)
84+
expect(components.has(1)).toBe(false)
85+
expect(components.get(2)).toBe('Mesh Renderer')
86+
expect(components.get(3)).toBe('Visibility')
87+
})
88+
89+
it('should return empty map when sdk is null', () => {
90+
useContextMock.mockReturnValueOnce({ sdk: null })
91+
92+
const { result } = renderHook(() => useEntityComponent())
93+
94+
const components = result.current.getComponents(testEntity)
95+
96+
expect(components.size).toBe(0)
97+
})
98+
})
99+
100+
describe('addComponent', () => {
101+
it('should add component when component is not already on entity', async () => {
102+
const componentId = 2
103+
const value = { someProperty: 'test' }
104+
105+
mockSdk.engine.getComponentOrNull.mockReturnValue({
106+
componentId: 2,
107+
componentName: 'TestComponent',
108+
has: jest.fn().mockReturnValue(false)
109+
})
110+
111+
const { result } = renderHook(() => useEntityComponent())
112+
113+
await act(async () => {
114+
result.current.addComponent(testEntity, componentId, value)
115+
})
116+
117+
expect(mockSdk.operations.addComponent).toHaveBeenCalledWith(testEntity, componentId, value)
118+
expect(mockSdk.operations.updateSelectedEntity).toHaveBeenCalledWith(testEntity)
119+
expect(mockSdk.operations.dispatch).toHaveBeenCalled()
120+
})
121+
122+
it('should not add component when component already exists on entity', async () => {
123+
const componentId = 2
124+
125+
mockSdk.engine.getComponentOrNull.mockReturnValue({
126+
componentId: 2,
127+
componentName: 'TestComponent',
128+
has: jest.fn().mockReturnValue(true)
129+
})
130+
131+
const { result } = renderHook(() => useEntityComponent())
132+
133+
await act(async () => {
134+
result.current.addComponent(testEntity, componentId)
135+
})
136+
137+
expect(mockSdk.operations.addComponent).not.toHaveBeenCalled()
138+
})
139+
140+
it('should not add component when sdk is null', async () => {
141+
useContextMock.mockReturnValueOnce({ sdk: null })
142+
143+
const { result } = renderHook(() => useEntityComponent())
144+
145+
await act(async () => {
146+
result.current.addComponent(testEntity, 2)
147+
})
148+
149+
expect(mockSdk.operations.addComponent).not.toHaveBeenCalled()
150+
})
151+
})
152+
153+
describe('removeComponent', () => {
154+
it('should remove component when sdk is available', async () => {
155+
const mockComponent = { componentId: 2, componentName: 'TestComponent' }
156+
157+
const { result } = renderHook(() => useEntityComponent())
158+
159+
await act(async () => {
160+
result.current.removeComponent(testEntity, mockComponent as any)
161+
})
162+
163+
expect(mockSdk.operations.removeComponent).toHaveBeenCalledWith(testEntity, mockComponent)
164+
expect(mockSdk.operations.updateSelectedEntity).toHaveBeenCalledWith(testEntity)
165+
expect(mockSdk.operations.dispatch).toHaveBeenCalled()
166+
})
167+
168+
it('should not remove component when sdk is null', async () => {
169+
useContextMock.mockReturnValueOnce({ sdk: null })
170+
171+
const { result } = renderHook(() => useEntityComponent())
172+
173+
await act(async () => {
174+
result.current.removeComponent(testEntity, {} as any)
175+
})
176+
177+
expect(mockSdk.operations.removeComponent).not.toHaveBeenCalled()
178+
})
179+
})
180+
181+
describe('getAvailableComponents', () => {
182+
it('should recalculate components when state is reset to null', () => {
183+
const { result } = renderHook(() => useEntityComponent())
184+
185+
result.current.getAvailableComponents(testEntity)
186+
187+
act(() => {
188+
;(global as any).triggerChange({
189+
component: { componentId: 1 },
190+
operation: CrdtMessageType.PUT_COMPONENT
191+
})
192+
})
193+
194+
result.current.getAvailableComponents(testEntity)
195+
196+
expect(mockSdk.engine.componentsIter).toHaveBeenCalledTimes(2)
197+
})
198+
199+
it('should return empty array when sdk is null', () => {
200+
useContextMock.mockReturnValueOnce({ sdk: null })
201+
202+
const { result } = renderHook(() => useEntityComponent())
203+
const components = result.current.getAvailableComponents(testEntity)
204+
205+
expect(components).toEqual([])
206+
})
207+
})
208+
209+
describe('component state updates', () => {
210+
it('should reset available components state on PUT_COMPONENT', () => {
211+
const { result } = renderHook(() => useEntityComponent())
212+
213+
result.current.getAvailableComponents(testEntity)
214+
expect(mockSdk.engine.componentsIter).toHaveBeenCalledTimes(1)
215+
216+
act(() => {
217+
;(global as any).triggerChange({
218+
component: { componentId: 1 },
219+
operation: CrdtMessageType.PUT_COMPONENT
220+
})
221+
})
222+
223+
result.current.getAvailableComponents(testEntity)
224+
expect(mockSdk.engine.componentsIter).toHaveBeenCalledTimes(2)
225+
})
226+
227+
it('should reset available components state on DELETE_COMPONENT', () => {
228+
const { result } = renderHook(() => useEntityComponent())
229+
230+
result.current.getAvailableComponents(testEntity)
231+
expect(mockSdk.engine.componentsIter).toHaveBeenCalledTimes(1)
232+
233+
act(() => {
234+
;(global as any).triggerChange({
235+
component: { componentId: 1 },
236+
operation: CrdtMessageType.DELETE_COMPONENT
237+
})
238+
})
239+
240+
result.current.getAvailableComponents(testEntity)
241+
expect(mockSdk.engine.componentsIter).toHaveBeenCalledTimes(2)
242+
})
243+
})
244+
})

0 commit comments

Comments
 (0)