Skip to content

Commit 9a17553

Browse files
committed
refactor: update tests
1 parent 97abab6 commit 9a17553

12 files changed

Lines changed: 431 additions & 36 deletions

File tree

src/compute.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ReadableImpl } from "./readable";
2-
import { type Get, type Readable, type Config, type OwnedReadable } from "./typings";
2+
import { type Config, type Get, type OwnedReadable, type Readable } from "./typings";
33
import { isReadable } from "./utils";
44

55
export interface ComputeFn<TValue> {
@@ -9,8 +9,6 @@ export interface ComputeFn<TValue> {
99
export const compute = <TValue>(fn: ComputeFn<TValue>, config?: Config<TValue>): OwnedReadable<TValue> => {
1010
let running: boolean | undefined;
1111

12-
let self: ReadableImpl<TValue>;
13-
1412
const get = <T = any>($?: Readable<T> | T | { $: Readable<T> }): T | undefined => {
1513
if (!isReadable($)) {
1614
return $ as T | undefined;
@@ -21,9 +19,7 @@ export const compute = <TValue>(fn: ComputeFn<TValue>, config?: Config<TValue>):
2119
return $.get();
2220
};
2321

24-
return new ReadableImpl(v => {
25-
self = v;
26-
22+
const self = new ReadableImpl(self => {
2723
const isFirst = !running;
2824
running = true;
2925

@@ -41,4 +37,6 @@ export const compute = <TValue>(fn: ComputeFn<TValue>, config?: Config<TValue>):
4137
}
4238
}
4339
}, config);
40+
41+
return self;
4442
};

src/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const on = (eventObject: EventObject, listener: Listener): RemoveListener
2626
eventObject.single_ || eventObject.multi_
2727
? (eventObject.multi_ ??= new Set<Listener>().add(eventObject.single_!)).add(listener)
2828
: (eventObject.single_ = listener),
29-
off.bind(null, eventObject, listener)
29+
() => off(eventObject, listener)
3030
);
3131

3232
export const off = (eventObject: EventObject, listener: Listener): boolean =>

src/readable.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ export class ReadableImpl<TValue = any> implements BatchTask {
270270

271271
public reaction(subscriber: Subscriber<TValue>): Disposer {
272272
this.onReaction_(subscriber);
273-
return this.unsubscribe.bind(this, subscriber);
273+
return () => this.unsubscribe(subscriber);
274274
}
275275

276276
public removeDep_(dep: ReadableImpl): void {
@@ -313,12 +313,8 @@ export class ReadableImpl<TValue = any> implements BatchTask {
313313
return "" + this.toJSON();
314314
}
315315

316-
public unsubscribe(subscriber: (...args: any[]) => any): void {
317-
this._subs_?.delete(subscriber);
318-
}
319-
320-
public unsubscribeAll(): void {
321-
this._subs_?.clear();
316+
public unsubscribe(subscriber?: (...args: any[]) => any): void {
317+
subscriber ? this._subs_?.delete(subscriber) : this._subs_?.clear();
322318
}
323319

324320
public valueOf(): TValue {

src/typings.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,18 @@ export interface Readable<TValue = any> {
7171
*/
7272
subscribe(subscriber: Subscriber<TValue>): Disposer;
7373
/**
74-
* Remove the given subscriber.
75-
* @param subscriber
74+
* Remove the given subscriber or all subscribers if no subscriber is provided.
75+
* @param subscriber Optional subscriber function to remove.
76+
* If not provided, all subscribers will be removed.
7677
*/
77-
unsubscribe(subscriber: (...args: any[]) => any): void;
78+
unsubscribe(subscriber?: (...args: any[]) => any): void;
7879
}
7980

8081
export interface OwnedReadable<TValue = any> extends Readable<TValue> {
8182
/**
8283
* Remove all subscribers and lock.
8384
*/
8485
dispose(): void;
85-
/**
86-
* Remove all subscribers.
87-
*/
88-
unsubscribeAll(): void;
8986
}
9087

9188
export type SetValue<TValue = any> = (value: TValue) => void;
@@ -111,8 +108,4 @@ export interface OwnedWritable<TValue = any> extends Writable<TValue> {
111108
* Remove all subscribers and lock.
112109
*/
113110
dispose(): void;
114-
/**
115-
* Remove all subscribers.
116-
*/
117-
unsubscribeAll(): void;
118111
}

src/utils.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type $sValueTuple, type Readable, type Version } from "./typings";
1+
import { type $sValueTuple, type Readable } from "./typings";
22

33
export const BRAND = /* @__PURE__ */ Symbol.for("@embra/reactivity");
44
export type BRAND = typeof BRAND;
@@ -15,7 +15,7 @@ export type UNIQUE_VALUE = typeof UNIQUE_VALUE;
1515
*/
1616
export const unsubscribe = (
1717
$: Iterable<Readable> | Readable | null | undefined,
18-
subscriber: (...args: any[]) => any,
18+
subscriber?: (...args: any[]) => any,
1919
): void => {
2020
if ($) {
2121
if (isReadable($)) {
@@ -69,8 +69,6 @@ const getValue = <TValue>($: Readable<TValue>): TValue => $.value;
6969
export const getValues = <T extends readonly Readable[]>($s: T): [...$sValueTuple<T>] =>
7070
$s.map(getValue) as [...$sValueTuple<T>];
7171

72-
export const getVersion = ($: Readable): Version => $.$version;
73-
7472
interface IsReadable {
7573
<T extends Readable>($: T): $ is T;
7674
($: unknown): $ is Readable;

src/watch.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ export const watch = (effect: WatchEffect): Disposer => {
4848
};
4949

5050
const runner = () => {
51-
if (disposed) {
52-
return;
53-
}
54-
5551
if (cleanupEffect) {
5652
const cleanup = cleanupEffect;
5753
cleanupEffect = null;

test/collections/reactiveMap.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,76 @@ describe("ReactiveMap", () => {
496496
consoleErrorMock.mockRestore();
497497
});
498498
});
499+
500+
describe("onChanged", () => {
501+
it("should notify multiple listeners on change", () => {
502+
const map = reactiveMap<string, number>();
503+
const listener1 = vi.fn();
504+
const listener2 = vi.fn();
505+
506+
map.onChanged(listener1);
507+
const dispose = map.onChanged(listener2);
508+
509+
map.set("foo", 1);
510+
expect(listener1).toHaveBeenCalledWith({ upsert: [["foo", 1]], delete: [] });
511+
expect(listener2).toHaveBeenCalledWith({ upsert: [["foo", 1]], delete: [] });
512+
513+
listener1.mockClear();
514+
listener2.mockClear();
515+
516+
dispose();
517+
518+
map.set("bar", 2);
519+
expect(listener1).toHaveBeenCalledWith({ upsert: [["bar", 2]], delete: [] });
520+
expect(listener2).toHaveBeenCalledTimes(0);
521+
});
522+
523+
it("should not notify listeners after dispose", () => {
524+
const map = reactiveMap<string, number>();
525+
const listener = vi.fn();
526+
527+
map.onChanged(listener);
528+
map.dispose();
529+
530+
map.set("foo", 1);
531+
expect(listener).toHaveBeenCalledTimes(0);
532+
});
533+
534+
it("should not notify disposed listeners", () => {
535+
const map = reactiveMap<string, number>();
536+
const listener = vi.fn();
537+
538+
const dispose = map.onChanged(listener);
539+
dispose();
540+
541+
map.set("foo", 1);
542+
expect(listener).toHaveBeenCalledTimes(0);
543+
});
544+
});
545+
546+
describe("onDisposeValue", () => {
547+
it("should notify listeners when a value should be disposed", () => {
548+
const map = reactiveMap<string, number>();
549+
const listener1 = vi.fn();
550+
const listener2 = vi.fn();
551+
552+
map.onDisposeValue(listener1);
553+
const dispose = map.onDisposeValue(listener2);
554+
555+
map.set("foo", 1);
556+
map.delete("foo");
557+
expect(listener1).toHaveBeenCalledWith(1);
558+
expect(listener2).toHaveBeenCalledWith(1);
559+
560+
listener1.mockClear();
561+
listener2.mockClear();
562+
563+
dispose();
564+
565+
map.set("bar", 2);
566+
map.delete("bar");
567+
expect(listener1).toHaveBeenCalledWith(2);
568+
expect(listener2).toHaveBeenCalledTimes(0);
569+
});
570+
});
499571
});

test/event.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
import { on, send, off, size } from "../src/event";
4+
5+
describe("event", () => {
6+
it("should send data to single listener", () => {
7+
const eventObject = {};
8+
const listener = vi.fn();
9+
10+
on(eventObject, listener);
11+
send(eventObject, "test data");
12+
13+
expect(listener).toBeCalledTimes(1);
14+
expect(listener).toBeCalledWith("test data");
15+
});
16+
17+
it("should send data to multiple listeners", () => {
18+
const eventObject = {};
19+
const listener1 = vi.fn();
20+
const listener2 = vi.fn();
21+
22+
on(eventObject, listener1);
23+
on(eventObject, listener2);
24+
send(eventObject, "test data");
25+
26+
expect(listener1).toBeCalledTimes(1);
27+
expect(listener1).toBeCalledWith("test data");
28+
expect(listener2).toBeCalledTimes(1);
29+
expect(listener2).toBeCalledWith("test data");
30+
});
31+
32+
it("should return the correct size of listeners", () => {
33+
const eventObject: any = {};
34+
const listener1 = vi.fn();
35+
const listener2 = vi.fn();
36+
37+
expect(size(eventObject)).toBe(0);
38+
39+
on(eventObject, listener1);
40+
expect(size(eventObject)).toBe(1);
41+
42+
on(eventObject, listener2);
43+
expect(size(eventObject)).toBe(2);
44+
45+
off(eventObject, listener1);
46+
expect(size(eventObject)).toBe(1);
47+
48+
off(eventObject, listener2);
49+
expect(size(eventObject)).toBe(0);
50+
});
51+
52+
it("should remove a single listener", () => {
53+
const eventObject: any = {};
54+
const listener = vi.fn();
55+
56+
on(eventObject, listener);
57+
expect(off(eventObject, listener)).toBe(true);
58+
expect(size(eventObject)).toBe(0);
59+
60+
// Trying to remove the same listener again should return false
61+
expect(off(eventObject, listener)).toBe(false);
62+
});
63+
64+
it("should notify all listeners when some of them throw", () => {
65+
const eventObject: any = {};
66+
const listener1 = vi.fn(() => {
67+
throw new Error("Listener 1 error");
68+
});
69+
const listener2 = vi.fn();
70+
71+
on(eventObject, listener1);
72+
on(eventObject, listener2);
73+
74+
expect(() => send(eventObject, "test data")).toThrow("Listener 1 error");
75+
76+
expect(listener1).toBeCalledTimes(1);
77+
expect(listener1).toBeCalledWith("test data");
78+
expect(listener2).toBeCalledTimes(1);
79+
expect(listener2).toBeCalledWith("test data");
80+
});
81+
});

0 commit comments

Comments
 (0)