Skip to content

Commit bbade69

Browse files
committed
add tests and docs for emit from ShadowObjectCreationAPI
1 parent 14aecac commit bbade69

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {expect} from '@esm-bundle/chai';
2+
import {on, once} from '@spearwolf/eventize';
3+
import {ComponentContext} from '@spearwolf/shadow-objects';
4+
import {shadowObjects} from '@spearwolf/shadow-objects/shadow-objects.js';
5+
import '@spearwolf/shadow-objects/shae-ent.js';
6+
import '@spearwolf/shadow-objects/shae-worker.js';
7+
import {findElementsById} from '../../src/findElementsById.js';
8+
import {render} from '../../src/render.js';
9+
10+
describe('ShadowObjectCreationAPI.emit helper', () => {
11+
beforeEach(async () => {
12+
render(`
13+
<shae-worker local no-autostart auto-sync="no" id="localEnv" no-structured-clone></shae-worker>
14+
15+
<shae-ent id="a" token="A"></shae-ent>
16+
<shae-ent id="b" token="B"></shae-ent>
17+
`);
18+
19+
await Promise.all(['shae-worker', 'shae-ent'].map((name) => customElements.whenDefined(name)));
20+
});
21+
22+
afterEach(() => {
23+
ComponentContext.get().clear();
24+
const localEnv = document.getElementById('localEnv');
25+
if (localEnv && localEnv.shadowEnv) {
26+
localEnv.shadowEnv.destroy();
27+
}
28+
});
29+
30+
it('emits events on the entity by default', async () => {
31+
let emitted = false;
32+
33+
class MyShadowObject {
34+
constructor({entity, emit}) {
35+
once(entity, 'foo', () => {
36+
emitted = true;
37+
});
38+
emit('foo');
39+
}
40+
}
41+
42+
shadowObjects.define('A', MyShadowObject);
43+
44+
const [localEnv] = findElementsById('localEnv');
45+
await localEnv.start();
46+
await localEnv.shadowEnv.syncWait();
47+
48+
expect(emitted).to.be.true;
49+
});
50+
51+
it('emits events on a specific target if provided', async () => {
52+
let emitted = false;
53+
54+
class MyShadowObject {
55+
constructor({emit}) {
56+
const otherObj = {};
57+
on(otherObj, 'bar', () => {
58+
emitted = true;
59+
});
60+
emit(otherObj, 'bar');
61+
}
62+
}
63+
64+
shadowObjects.define('B', MyShadowObject);
65+
66+
const [localEnv] = findElementsById('localEnv');
67+
await localEnv.start();
68+
await localEnv.shadowEnv.syncWait();
69+
70+
expect(emitted).to.be.true;
71+
});
72+
});

packages/shadow-objects/docs/03-api/01-shadow-object-api.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ Listens for an event on a source.
153153

154154
#### Listening to View Events
155155

156-
### `on(entity, onViewEvent, callback)`
157-
### `onViewEvent(callback)`
156+
##### `on(entity, onViewEvent, callback)`
157+
##### `onViewEvent(callback)`
158158

159159
To listen to events dispatched from the DOM (View Layer), listen to the special event name `onViewEvent` on the `entity` target.
160160

@@ -187,6 +187,27 @@ function ShadowObject({ on, onViewEvent }: ShadowObjectCreationAPI) {
187187

188188
Same as `on`, but the listener is automatically removed after the first trigger. Like `on`, if the first argument is a string, symbol, or array of strings/symbols, the `entity` is used as the event source.
189189

190+
### `emit(eventNames, ...eventArgs)`
191+
192+
Emits an event on the *entity* associated with the current shadow object.
193+
194+
- **`eventNames`** (`string` | `string[]`): The name(s) of the event(s) to emit.
195+
- **`...eventArgs`** (`any[]`): Arguments to pass to the event listeners.
196+
197+
### `emit(target, eventNames, ...eventArgs)`
198+
199+
Emits an event on a specific *target* object.
200+
201+
- **`target`** (`Object`): The object to emit the event on. Must be compatible with the event system.
202+
- **`eventNames`** (`string` | `string[]`): The name(s) of the event(s) to emit.
203+
- **`...eventArgs`** (`any[]`): Arguments to pass to the event listeners.
204+
205+
#### Best Practices
206+
207+
* **Use the default signature** (`emit('name')`) for events that represent the state or actions of the component itself. This allows parent components or other systems to listen to the entity easily.
208+
* **Avoid tight coupling.** Use events to notify about changes rather than directly calling methods on other objects when possible.
209+
210+
190211
---
191212

192213
## 5. View Integration

packages/shadow-objects/src/in-the-dark/Kernel.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -628,10 +628,11 @@ export class Kernel {
628628
const [firstArg] = args;
629629
if (typeof firstArg === 'string' || typeof firstArg === 'symbol' || Array.isArray(firstArg)) {
630630
// @ts-ignore
631-
return emit(entry.entity, ...args);
631+
emit(entry.entity, ...args);
632+
} else {
633+
// @ts-ignore
634+
emit(...args);
632635
}
633-
// @ts-ignore
634-
emit(...args);
635636
},
636637

637638
onViewEvent(callback: (type: string, data: unknown) => any) {

packages/shadow-objects/src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,20 @@ export interface ShadowObjectCreationAPI {
150150

151151
onViewEvent(callback: (type: string, data: unknown) => any): void;
152152

153+
/**
154+
* Emit an event on the *entity* associated with this shadow object.
155+
*
156+
* @param eventNames - The name(s) of the event(s) to emit.
157+
* @param eventArgs - Arguments to pass to the event listeners.
158+
*/
153159
emit(eventNames: AnyEventNames, ...eventArgs: EventArgs): void;
160+
/**
161+
* Emit an event on a specific *target* object.
162+
*
163+
* @param target - The object to emit the event on.
164+
* @param eventNames - The name(s) of the event(s) to emit.
165+
* @param eventArgs - Arguments to pass to the event listeners.
166+
*/
154167
emit(target: EventizedObject, eventNames: AnyEventNames, ...eventArgs: EventArgs): void;
155168

156169
onDestroy(callback: () => any): void;

0 commit comments

Comments
 (0)