Skip to content

Commit 15575d7

Browse files
Sascha DobschalSascha Dobschal
authored andcommitted
Support for deep/non-deep observables
1 parent 215a73e commit 15575d7

File tree

8 files changed

+91
-11
lines changed

8 files changed

+91
-11
lines changed

Computed.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ interface ComputedValue<T> {
55
unsubscribe(callback: (value: any) => void): void;
66
}
77

8-
export default function Computed<T>(fn: () => T): ComputedValue<T>;
8+
export default function Computed<T>(fn: () => T, deep?: boolean): ComputedValue<T>;

Computed.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const Observable = require("./Observable.js");
22

3-
function Computed(fn) {
3+
function Computed(fn, deep = false) {
44

5-
const observable = Observable();
5+
const observable = Observable(undefined, deep);
66

77
const computed = {
88
isObservable: true,

Computed.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,47 @@ test("The value of a Computeds function cannot be set", () => {
7979
expect(() => { computed.value = 10; }).toThrow();
8080
});
8181

82+
test("Non-deep computed is not affected by changes to the observables properties", () => {
83+
const observable = Observable({ a: 5 }, true);
84+
const computed = Computed(() => observable.value, false);
85+
const mockedSubscriber = jest.fn();
86+
computed.subscribe(mockedSubscriber);
87+
expect(computed.value.a).toBe(5);
88+
computed.value.a = 10;
89+
expect(computed.value.a).toBe(10);
90+
expect(mockedSubscriber).toHaveBeenCalledTimes(1);
91+
});
92+
93+
test("Non-deep computed is not affected by changes to the observables properties", () => {
94+
const observable = Observable({ a: 5 }, false);
95+
const computed = Computed(() => observable.value, true);
96+
const mockedSubscriber = jest.fn();
97+
computed.subscribe(mockedSubscriber);
98+
expect(computed.value.a).toBe(5);
99+
observable.value.a = 10;
100+
expect(computed.value.a).toBe(10);
101+
expect(mockedSubscriber).toHaveBeenCalledTimes(1);
102+
});
103+
104+
test("deep computed is affected by changes to the observables properties", () => {
105+
const observable = Observable({ a: 5 }, false);
106+
const computed = Computed(() => observable.value, true);
107+
const mockedSubscriber = jest.fn();
108+
computed.subscribe(mockedSubscriber);
109+
expect(computed.value.a).toBe(5);
110+
computed.value.a = 10;
111+
expect(computed.value.a).toBe(10);
112+
expect(mockedSubscriber).toHaveBeenCalledTimes(2);
113+
});
114+
115+
test("deep computed is affected by changes to the observables properties", () => {
116+
const observable = Observable({ a: 5 }, true);
117+
const computed = Computed(() => observable.value, true);
118+
const mockedSubscriber = jest.fn();
119+
computed.subscribe(mockedSubscriber);
120+
expect(computed.value.a).toBe(5);
121+
observable.value.a = 10;
122+
expect(computed.value.a).toBe(10);
123+
expect(mockedSubscriber).toHaveBeenCalledTimes(2);
124+
});
125+

Observable.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ interface ObservableVariable<T> {
66
map(callback: (value: any) => any): ObservableVariable<any>;
77
}
88

9-
export default function Observable<T>(initialValue: T): ObservableVariable<T>;
9+
export default function Observable<T>(initialValue: T, deep?: boolean): ObservableVariable<T>;

Observable.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function Observable(initialValue) {
1+
function Observable(initialValue, deep = true) {
22
const listeners = new Set();
33
let value;
44

@@ -26,22 +26,25 @@ function Observable(initialValue) {
2626
}
2727

2828
function _setValue(newValue) {
29-
if (value !== newValue) {
29+
if (value === newValue) return;
30+
if(!deep) {
31+
value = newValue;
32+
} else {
3033
if (newValue?.constructor === Object) { // isPlainObject
3134
_observePlainObject(listeners, newValue);
3235
} else if (Array.isArray(newValue)) { // isArray
3336
_observeObjectModifiers(listeners, newValue, ["push", "sort", "unshift", "pop", "shift", "splice", "reverse"]);
3437
} else if (newValue instanceof Date) { // isDate
3538
_observeObjectModifiers(listeners, newValue, ["setDate", "setFullYear", "setHours", "setMilliseconds", "setMinutes", "setMonth", "setSeconds", "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds", "setUTCMinutes", "setUTCMonth", "setUTCSeconds"]);
36-
} else if(newValue instanceof HTMLElement) {
39+
} else if (newValue instanceof HTMLElement) {
3740
_observeObjectModifiers(listeners, newValue, ["appendChild", "remove", "replaceWith", "setAttribute", "removeAttribute"]);
3841
} else if (newValue !== Object(newValue)) { // isPrimitive
3942
value = newValue;
4043
} else {
4144
throw new Error("Observable does not support type: " + typeof newValue);
4245
}
43-
listeners.forEach(listener => listener(value));
4446
}
47+
listeners.forEach(listener => listener(value));
4548
}
4649

4750
_setValue(initialValue);

Observable.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,36 @@ test("Observing an HTMLElement works.", () => {
5959
observable.value.setAttribute("id", "test");
6060
expect(mockedSubscriber).toHaveBeenCalledTimes(2);
6161
});
62+
63+
test("Non-deep observables support any type.", () => {
64+
const observable = Observable(Promise, false);
65+
const mockedSubscriber = jest.fn();
66+
observable.subscribe(mockedSubscriber);
67+
observable.value = "test";
68+
expect(mockedSubscriber).toHaveBeenCalledTimes(2);
69+
});
70+
71+
test("Deep observables throw an error if the type isn't supported.", () => {
72+
expect(() => {
73+
const observable = Observable(Promise);
74+
const mockedSubscriber = jest.fn();
75+
observable.subscribe(mockedSubscriber);
76+
observable.value = "test";
77+
}).toThrow("Observable does not support type: function");
78+
});
79+
80+
test("Non-deep observables subscriber isn't called if property changes.", () => {
81+
const observable = Observable(document.createElement("div"), false);
82+
const mockedSubscriber = jest.fn();
83+
observable.subscribe(mockedSubscriber);
84+
observable.value.setAttribute("id", "test");
85+
expect(mockedSubscriber).toHaveBeenCalledTimes(1);
86+
});
87+
88+
test("Deep observables subscriber is called if property changes.", () => {
89+
const observable = Observable(document.createElement("div"));
90+
const mockedSubscriber = jest.fn();
91+
observable.subscribe(mockedSubscriber);
92+
observable.value.setAttribute("id", "test");
93+
expect(mockedSubscriber).toHaveBeenCalledTimes(2);
94+
});

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dobschal/observable",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"main": "index.js",
55
"scripts": {
66
"test": "jest",

0 commit comments

Comments
 (0)