Skip to content

Commit c60b68a

Browse files
committed
feat: add observable()
1 parent 0b810ea commit c60b68a

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {observable} from '../observable';
2+
3+
describe('observable()', () => {
4+
it('creates and object', () => {
5+
const obs = observable(123);
6+
expect(typeof obs).toBe('object');
7+
});
8+
it('if no initial value provided state equals undefined', () => {
9+
const obs = observable();
10+
expect(obs.get()).toBe(void 0);
11+
});
12+
it('sets state to the default specified value, .get() returns it', () => {
13+
const state = {};
14+
const obs = observable(state);
15+
expect(obs.get()).toBe(state);
16+
});
17+
it('returns mutated state', () => {
18+
const obs = observable();
19+
const state = {};
20+
obs.set(state);
21+
expect(obs.get()).toBe(state);
22+
});
23+
it('.sub() returns an unsub function', () => {
24+
const obs = observable();
25+
const unsub = obs.sub(() => {});
26+
expect(typeof unsub).toBe('function');
27+
});
28+
it('calls listener on on state change', () => {
29+
const listener = jest.fn();
30+
const obs = observable();
31+
obs.sub(listener);
32+
obs.set(123);
33+
expect(listener).toHaveBeenCalledTimes(1);
34+
});
35+
it('calls correct number of times', () => {
36+
const listener = jest.fn();
37+
const obs = observable();
38+
obs.sub(listener);
39+
obs.set(1);
40+
obs.set(2);
41+
obs.set(3);
42+
expect(listener).toHaveBeenCalledTimes(3);
43+
});
44+
it('calls listener with the correct data', () => {
45+
const listener = jest.fn();
46+
const obs = observable();
47+
obs.sub(listener);
48+
obs.set(1);
49+
obs.set(2);
50+
obs.set(3);
51+
expect(listener.mock.calls).toEqual([[1], [2], [3]]);
52+
});
53+
it('stops calling listener when unsubscribed', () => {
54+
const listener = jest.fn();
55+
const obs = observable();
56+
const unsub = obs.sub(listener);
57+
obs.set(1);
58+
obs.set(2);
59+
unsub();
60+
obs.set(3);
61+
expect(listener.mock.calls).toEqual([[1], [2]]);
62+
});
63+
});

src/context/observable.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export type TObservalbeUnsub = (() => void);
2+
export type TObservableSet<T> = (state: T) => void;
3+
export interface IObservable<T> {
4+
get: () => T;
5+
set: TObservableSet<T>;
6+
sub: (listener: TObservableSet<T>) => TObservalbeUnsub;
7+
}
8+
9+
export const observable: <T>(state?: T) => IObservable<T> = state => {
10+
let listeners = [];
11+
let currentState = state;
12+
13+
const get = () => currentState;
14+
15+
const set = state => {
16+
currentState = state;
17+
for (const listener of listeners) listener(currentState);
18+
};
19+
20+
const sub = listener => {
21+
listeners.push(listener);
22+
23+
return () => (listeners = listeners.filter(item => item !== listener));
24+
};
25+
26+
return {
27+
get,
28+
set,
29+
sub,
30+
};
31+
};

0 commit comments

Comments
 (0)