Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8d85085

Browse files
committed
add ngt test bed docs
1 parent d23ca5d commit 8d85085

File tree

6 files changed

+354
-0
lines changed

6 files changed

+354
-0
lines changed

apps/astro-docs/astro.config.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ export default defineConfig({
108108
{ label: 'Store', slug: 'core/api/store' },
109109
],
110110
},
111+
{
112+
label: 'Testing',
113+
collapsed: true,
114+
badge: { text: 'Preview', variant: 'caution' },
115+
items: [
116+
{ label: 'Introduction', slug: 'core/testing/introduction' },
117+
{ label: 'NgtTestBed', slug: 'core/testing/test-bed' },
118+
{ label: 'fireEvent', slug: 'core/testing/fire-event' },
119+
{ label: 'advance', slug: 'core/testing/advance' },
120+
],
121+
},
111122
{
112123
label: 'Utilities',
113124
collapsed: true,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
CUSTOM_ELEMENTS_SCHEMA,
5+
type ElementRef,
6+
signal,
7+
viewChild,
8+
} from '@angular/core';
9+
import { extend, injectBeforeRender, NgtCanvas } from 'angular-three';
10+
import type { Mesh } from 'three';
11+
import * as THREE from 'three';
12+
13+
extend(THREE);
14+
15+
@Component({
16+
standalone: true,
17+
template: `
18+
<ngt-mesh
19+
#mesh
20+
[scale]="clicked() ? 1.5 : 1"
21+
(click)="clicked.set(!clicked())"
22+
(pointerover)="hovered.set(true)"
23+
(pointerout)="hovered.set(false)"
24+
>
25+
<ngt-box-geometry />
26+
<ngt-mesh-basic-material [color]="hovered() ? 'hotpink' : 'orange'" />
27+
</ngt-mesh>
28+
`,
29+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
30+
changeDetection: ChangeDetectionStrategy.OnPush,
31+
})
32+
class SceneGraph {
33+
hovered = signal(false);
34+
clicked = signal(false);
35+
36+
meshRef = viewChild.required<ElementRef<Mesh>>('mesh');
37+
38+
constructor() {
39+
injectBeforeRender(() => {
40+
const mesh = this.meshRef().nativeElement;
41+
mesh.rotation.x += 0.01;
42+
});
43+
}
44+
}
45+
46+
@Component({
47+
standalone: true,
48+
template: `
49+
<ngt-canvas [sceneGraph]="sceneGraph" />
50+
`,
51+
imports: [NgtCanvas],
52+
changeDetection: ChangeDetectionStrategy.OnPush,
53+
host: { class: 'test-bed' },
54+
})
55+
export default class TestBed {
56+
sceneGraph = SceneGraph;
57+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
title: advance
3+
description: Details about the Angular Three Testing `advance` method
4+
---
5+
6+
`advance` is a method on `NgtTestBed` that allows us to advance frame to run animations in the scene graph.
7+
8+
### `advance(frames, delta)`
9+
10+
`advance` accepts two arguments:
11+
12+
- `frames` is the number of frames to advance
13+
- `delta` is the delta time to use for the animations. It can be a number or an array of numbers.
14+
15+
```ts
16+
const { fixture, advance } = NgtTestBed.create(SceneGraph);
17+
18+
await advance(1);
19+
// assert the scene graph state after 1 frame
20+
```
21+
22+
## Example Scenario
23+
24+
For this example, we will use `advance` to test the animations.
25+
26+
```diff lang="ts"
27+
import { NgtTestBed } from 'angular-three/testing';
28+
29+
describe('SceneGraph', () => {
30+
it('should render', async () => {
31+
const { scene, fireEvent, advance } = NgtTestBed.create(SceneGraph);
32+
fireEvent.setAutoDetectChanges(true);
33+
34+
expect(scene.children.length).toEqual(1);
35+
const mesh = scene.children[0] as Mesh;
36+
expect(mesh.isMesh).toEqual(true);
37+
38+
expect(material.color.getHexString()).toEqual('ffa500');
39+
40+
await fireEvent(mesh, 'pointerover');
41+
expect(material.color.getHexString()).toEqual('ff69b4');
42+
43+
await fireEvent(mesh, 'pointerout');
44+
expect(material.color.getHexString()).toEqual('ffa500');
45+
46+
await fireEvent(mesh, 'click');
47+
expect(mesh.scale.toArray()).toEqual([1.5, 1.5, 1.5]);
48+
49+
+ expect(mesh.rotation.x).toEqual(0);
50+
+ await advance(1);
51+
52+
// the cube should have rotated by 0.01 on the X axis since we advanced 1 frame
53+
+ expect(mesh.rotation.x).toEqual(0.01);
54+
});
55+
});
56+
```
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: fireEvent
3+
description: Details about the Angular Three Testing `fireEvent` method
4+
---
5+
6+
`fireEvent` is a method on `NgtTestBed` that allows us to fire events on any element in the scene graph.
7+
8+
### `fireEvent(element, eventName, eventData)`
9+
10+
`fireEvent` accepts three arguments:
11+
12+
- `element` is the element to fire the event on
13+
- `eventName` is the name of the event to fire. Must be events that are supported by Angular Three events system.
14+
- `eventData` is an optional object that contains the event data
15+
16+
```ts
17+
const { fixture, fireEvent } = NgtTestBed.create(SceneGraph);
18+
19+
await fireEvent(mesh, 'click');
20+
fixture.detectChanges();
21+
22+
await fireEvent(mesh, 'pointerover');
23+
fixture.detectChanges();
24+
```
25+
26+
#### `fireEvent.setAutoDetectChanges(auto: boolean)`
27+
28+
After firing an event, we need to call `fixture.detectChanges()` to flush any changes that may have occurred (e.g: signal state changes).
29+
30+
We can use `fireEvent.setAutoDetectChanges(true)` to automatically call `fixture.detectChanges()` after firing an event.
31+
This toggles an internal flag whose life-cycle is tied to the scope of the test. If we want to disable auto-detection anytime, we can call `fireEvent.setAutoDetectChanges(false)`.
32+
33+
```ts
34+
const { fireEvent } = NgtTestBed.create(SceneGraph);
35+
fireEvent.setAutoDetectChanges(true);
36+
37+
await fireEvent(mesh, 'click');
38+
await fireEvent(mesh, 'pointerover');
39+
```
40+
41+
## Example Scenario
42+
43+
For this example, we will use `fireEvent` to fire `pointerover`, `pointerout`, and `click` events on the cube
44+
and assert the cube's state after each event.
45+
46+
```diff lang="ts"
47+
import { NgtTestBed } from 'angular-three/testing';
48+
49+
describe('SceneGraph', () => {
50+
it('should render', async () => {
51+
const { scene, fireEvent, advance } = NgtTestBed.create(SceneGraph);
52+
+ fireEvent.setAutoDetectChanges(true);
53+
54+
expect(scene.children.length).toEqual(1);
55+
const mesh = scene.children[0] as Mesh;
56+
expect(mesh.isMesh).toEqual(true);
57+
58+
+ expect(material.color.getHexString()).toEqual('ffa500');
59+
60+
+ await fireEvent(mesh, 'pointerover');
61+
+ expect(material.color.getHexString()).toEqual('ff69b4');
62+
63+
+ await fireEvent(mesh, 'pointerout');
64+
+ expect(material.color.getHexString()).toEqual('ffa500');
65+
66+
+ await fireEvent(mesh, 'click');
67+
+ expect(mesh.scale.toArray()).toEqual([1.5, 1.5, 1.5]);
68+
});
69+
});
70+
```
71+
72+
:::note
73+
74+
We use `color.getHexString()` to compare the color because `material.color` is a `Color` instance, even though we use CSS Color string in the template.
75+
76+
:::
77+
78+
Last but not least, we will use `advance` to test the animations.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: Introduction
3+
description: Introduction to the Angular Three Testing API
4+
---
5+
6+
7+
import TestBed from '../../../../components/testing/test-bed';
8+
9+
:::caution
10+
11+
This API is in Developer Preview and is subject to change without following semantic versioning.
12+
13+
:::
14+
15+
Angular Three Testing provides a set of utilities to help us write unit tests for the scene graphs built with Angular Three.
16+
17+
In test environment, we do not **actually** render the scene graph. Instead, we assert the state of the scene graph against the expected state
18+
to ensure that the Angular Three renderer works as expected.
19+
20+
## Example Scenario
21+
22+
Assuming we have the following `SceneGraph`
23+
24+
```angular-ts
25+
@Component({
26+
standalone: true,
27+
template: `
28+
<ngt-mesh
29+
#mesh
30+
[scale]="clicked() ? 1.5 : 1"
31+
(click)="clicked.set(!clicked())"
32+
(pointerover)="hovered.set(true)"
33+
(pointerout)="hovered.set(false)"
34+
>
35+
<ngt-box-geometry />
36+
<ngt-mesh-basic-material [color]="hovered() ? 'hotpink' : 'orange'" />
37+
</ngt-mesh>
38+
`,
39+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
40+
changeDetection: ChangeDetectionStrategy.OnPush,
41+
})
42+
class SceneGraph {
43+
hovered = signal(false);
44+
clicked = signal(false);
45+
46+
meshRef = viewChild.required<ElementRef<Mesh>>('mesh')
47+
48+
constructor() {
49+
injectBeforeRender(() => {
50+
const mesh = this.meshRef().nativeElement;
51+
mesh.rotation.x += 0.01;
52+
})
53+
}
54+
}
55+
```
56+
57+
The _rendered_ result of the above scene graph is as follows:
58+
59+
<div class="h-96 w-full border border-dashed border-accent-500 rounded">
60+
<TestBed client:only />
61+
</div>
62+
63+
Our goal is to test the `SceneGraph` component and assert:
64+
65+
- The mesh is rendered
66+
- The material color changes when the mesh is hovered
67+
- The mesh scales when the mesh is clicked
68+
- The mesh rotates by 0.01 radians per frame
69+
70+
First, let's look at `NgtTestBed`
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: NgtTestBed
3+
description: Details about the Angular Three `NgtTestBed` utility
4+
---
5+
6+
`NgtTestBed` is a utility from `angular-three/testing` that abstracts `TestBed` to make it easier to test our scene graph components.
7+
8+
## `create()`
9+
10+
`NgtTestBed` exposes a single static method `create` that accepts a Component class and returns an object with the following properties:
11+
12+
```ts
13+
const ngtTestBed = NgtTestBed.create(SceneGraph);
14+
ngtTestBed.fixture; // ComponentFixture<NgtTestCanvas>
15+
ngtTestBed.store; // NgtSignalStore<NgtState>
16+
ngtTestBed.scene; // root THREE.Scene
17+
ngtTestBed.sceneInstanceNode; // root Scene local state
18+
ngtTestBed.canvas; // the mocked HTMLCanvasElement
19+
ngtTestBed.destroy; // method to destroy the fixture
20+
ngtTestBed.fireEvent; // method to fire events on an element in the scene graph
21+
ngtTestBed.advance; // method to advance the animation loop manually per frame
22+
```
23+
24+
25+
### Options
26+
27+
`create` accepts an optional second argument that allows us to pass in options to customize the `TestBed`.
28+
29+
```ts
30+
const ngtTestBed = NgtTestBed.create(SceneGraph, {
31+
mockCanvasOptions: { width: 1280, height: 720 },
32+
canvasConfiguration: {
33+
camera: { position: [0, 0, 5] },
34+
shadows: true
35+
},
36+
...TestBedOptions,
37+
});
38+
```
39+
40+
- `canvasConfiguration` is an object whose role is similar to `NgtCanvas` inputs.
41+
:::note
42+
43+
Options that determine the Color Space like `linear`, `flat`, `legacy` **can** affect the assertions in the test for THREE.js entities that are affected by different color spaces (e.g: `Color`)
44+
45+
:::
46+
- `mockCanvasOptions` is an object that allows us to customize the mocked canvas element. It accepts `width` and `height` as well as `beforeReturn` which is a function that allows us to return a mocked `HTMLCanvasElement` before the `TestBed` is created.
47+
48+
## Example Scenario
49+
50+
For this example, we will use `scene`, `fireEvent`, and `advance` to test the `SceneGraph` component.
51+
52+
- `scene` allows us to assert the state of the scene graph
53+
- `fireEvent` allows us to fire events on the cube
54+
- `advance` allows us to advance the animation loop manually per frame
55+
56+
```ts
57+
import { NgtTestBed } from 'angular-three/testing';
58+
59+
describe('SceneGraph', () => {
60+
it('should render', async () => {
61+
const { scene, fireEvent, advance } = NgtTestBed.create(SceneGraph);
62+
});
63+
});
64+
```
65+
66+
With `scene`, we can assert the state of the scene graph. We can assert
67+
however we want to. To keep things simple, we will just assert that the root Scene has a child which is a `Mesh`
68+
69+
```diff lang="ts"
70+
import { NgtTestBed } from 'angular-three/testing';
71+
72+
describe('SceneGraph', () => {
73+
it('should render', async () => {
74+
const { scene, fireEvent, advance } = NgtTestBed.create(SceneGraph);
75+
+ expect(scene.children.length).toEqual(1);
76+
+ const mesh = scene.children[0] as Mesh;
77+
+ expect(mesh.isMesh).toEqual(true);
78+
});
79+
});
80+
```
81+
82+
Next, we will test the `Mesh` events with `fireEvent`

0 commit comments

Comments
 (0)