mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] observer: move observer out of store
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Observer
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
export interface Observer {
|
||||||
|
rev: number;
|
||||||
|
allowMutations: boolean;
|
||||||
|
observe: (val: any) => void;
|
||||||
|
set: (target: any, key: number | string, value: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeObserver(): Observer {
|
||||||
|
const observer: Observer = {
|
||||||
|
rev: 1,
|
||||||
|
allowMutations: true,
|
||||||
|
observe: observe,
|
||||||
|
set: set
|
||||||
|
};
|
||||||
|
|
||||||
|
function set(target: any, key: number | string, value: any) {
|
||||||
|
addProp(target, key, value);
|
||||||
|
target.__owl__.rev++;
|
||||||
|
observer.rev++;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addProp<T extends { __owl__?: any }>(
|
||||||
|
obj: T,
|
||||||
|
key: string | number,
|
||||||
|
value: any
|
||||||
|
) {
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
set(newVal) {
|
||||||
|
if (!observer.allowMutations) {
|
||||||
|
throw new Error(
|
||||||
|
`State cannot be changed outside a mutation! (key: "${key}", val: "${newVal}")`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (newVal !== value) {
|
||||||
|
unobserve(value);
|
||||||
|
value = newVal;
|
||||||
|
observe(newVal, obj);
|
||||||
|
obj.__owl__.rev!++;
|
||||||
|
observer.rev++;
|
||||||
|
let parent = obj;
|
||||||
|
do {
|
||||||
|
parent.__owl__.deepRev++;
|
||||||
|
} while ((parent = parent.__owl__.parent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observe(value, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
function observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
||||||
|
const keys = Object.keys(obj);
|
||||||
|
obj.__owl__ = { rev: 1, deepRev: 1, parent };
|
||||||
|
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
||||||
|
for (let key of keys) {
|
||||||
|
addProp(obj, key, obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArrayProto = Array.prototype;
|
||||||
|
const ModifiedArrayProto = Object.create(ArrayProto);
|
||||||
|
|
||||||
|
const methodsToPatch = [
|
||||||
|
"push",
|
||||||
|
"pop",
|
||||||
|
"shift",
|
||||||
|
"unshift",
|
||||||
|
"splice",
|
||||||
|
"sort",
|
||||||
|
"reverse"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let method of methodsToPatch) {
|
||||||
|
const initialMethod = ArrayProto[method];
|
||||||
|
ModifiedArrayProto[method] = function(...args) {
|
||||||
|
observer.rev++;
|
||||||
|
this.__owl__.rev++;
|
||||||
|
let parent = this;
|
||||||
|
do {
|
||||||
|
parent.__owl__.deepRev++;
|
||||||
|
} while ((parent = parent.__owl__.parent));
|
||||||
|
let inserted;
|
||||||
|
switch (method) {
|
||||||
|
case "push":
|
||||||
|
case "unshift":
|
||||||
|
inserted = args;
|
||||||
|
break;
|
||||||
|
case "splice":
|
||||||
|
inserted = args.slice(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (inserted) {
|
||||||
|
for (let elem of inserted) {
|
||||||
|
observe(elem, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return initialMethod.call(this, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function observeArr(arr: Array<any>, parent?: any) {
|
||||||
|
(<any>arr).__owl__ = { rev: 1, deepRev: 1, parent };
|
||||||
|
Object.defineProperty(arr, "__owl__", { enumerable: false });
|
||||||
|
(<any>arr).__proto__ = ModifiedArrayProto;
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
observe(arr[i], arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function observe(value: any, parent?: any) {
|
||||||
|
if (value === null) {
|
||||||
|
// fun fact: typeof null === 'object'
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof value !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("__owl__" in value) {
|
||||||
|
// already observed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
observeArr(value, parent);
|
||||||
|
} else {
|
||||||
|
observeObj(value, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unobserve(target: any) {
|
||||||
|
if (target !== null && typeof target === "object") {
|
||||||
|
delete target.__owl__;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return observer;
|
||||||
|
}
|
||||||
+1
-143
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from "./component";
|
import { Component } from "./component";
|
||||||
import { EventBus } from "./event_bus";
|
import { EventBus } from "./event_bus";
|
||||||
|
import { makeObserver, Observer } from "./observer";
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Store Definition
|
// Store Definition
|
||||||
@@ -106,149 +107,6 @@ export class Store extends EventBus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// Observer
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
interface Observer {
|
|
||||||
rev: number;
|
|
||||||
allowMutations: boolean;
|
|
||||||
observe: (val: any) => void;
|
|
||||||
set: (target: any, key: number | string, value: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeObserver(): Observer {
|
|
||||||
const observer: Observer = {
|
|
||||||
rev: 1,
|
|
||||||
allowMutations: true,
|
|
||||||
observe: observe,
|
|
||||||
set: set
|
|
||||||
};
|
|
||||||
|
|
||||||
function set(target: any, key: number | string, value: any) {
|
|
||||||
addProp(target, key, value);
|
|
||||||
target.__owl__.rev++;
|
|
||||||
observer.rev++;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addProp<T extends { __owl__?: any }>(
|
|
||||||
obj: T,
|
|
||||||
key: string | number,
|
|
||||||
value: any
|
|
||||||
) {
|
|
||||||
Object.defineProperty(obj, key, {
|
|
||||||
enumerable: true,
|
|
||||||
get() {
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
set(newVal) {
|
|
||||||
if (!observer.allowMutations) {
|
|
||||||
throw new Error(
|
|
||||||
`State cannot be changed outside a mutation! (key: "${key}", val: "${newVal}")`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (newVal !== value) {
|
|
||||||
unobserve(value);
|
|
||||||
value = newVal;
|
|
||||||
observe(newVal, obj);
|
|
||||||
obj.__owl__.rev!++;
|
|
||||||
observer.rev++;
|
|
||||||
let parent = obj;
|
|
||||||
do {
|
|
||||||
parent.__owl__.deepRev++;
|
|
||||||
} while ((parent = parent.__owl__.parent));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observe(value, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
function observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
|
||||||
const keys = Object.keys(obj);
|
|
||||||
obj.__owl__ = { rev: 1, deepRev: 1, parent };
|
|
||||||
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
|
||||||
for (let key of keys) {
|
|
||||||
addProp(obj, key, obj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ArrayProto = Array.prototype;
|
|
||||||
const ModifiedArrayProto = Object.create(ArrayProto);
|
|
||||||
|
|
||||||
const methodsToPatch = [
|
|
||||||
"push",
|
|
||||||
"pop",
|
|
||||||
"shift",
|
|
||||||
"unshift",
|
|
||||||
"splice",
|
|
||||||
"sort",
|
|
||||||
"reverse"
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let method of methodsToPatch) {
|
|
||||||
const initialMethod = ArrayProto[method];
|
|
||||||
ModifiedArrayProto[method] = function(...args) {
|
|
||||||
observer.rev++;
|
|
||||||
this.__owl__.rev++;
|
|
||||||
let parent = this;
|
|
||||||
do {
|
|
||||||
parent.__owl__.deepRev++;
|
|
||||||
} while ((parent = parent.__owl__.parent));
|
|
||||||
let inserted;
|
|
||||||
switch (method) {
|
|
||||||
case "push":
|
|
||||||
case "unshift":
|
|
||||||
inserted = args;
|
|
||||||
break;
|
|
||||||
case "splice":
|
|
||||||
inserted = args.slice(2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (inserted) {
|
|
||||||
for (let elem of inserted) {
|
|
||||||
observe(elem, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return initialMethod.call(this, ...args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function observeArr(arr: Array<any>, parent?: any) {
|
|
||||||
(<any>arr).__owl__ = { rev: 1, deepRev: 1, parent };
|
|
||||||
Object.defineProperty(arr, "__owl__", { enumerable: false });
|
|
||||||
(<any>arr).__proto__ = ModifiedArrayProto;
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
observe(arr[i], arr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function observe(value: any, parent?: any) {
|
|
||||||
if (value === null) {
|
|
||||||
// fun fact: typeof null === 'object'
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof value !== "object") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ("__owl__" in value) {
|
|
||||||
// already observed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
observeArr(value, parent);
|
|
||||||
} else {
|
|
||||||
observeObj(value, parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unobserve(target: any) {
|
|
||||||
if (target !== null && typeof target === "object") {
|
|
||||||
delete target.__owl__;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Connect function
|
// Connect function
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import { makeObserver } from "../src/observer";
|
||||||
|
|
||||||
|
describe("observer", () => {
|
||||||
|
test("properly observe objects", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj: any = {};
|
||||||
|
|
||||||
|
observer.observe(obj);
|
||||||
|
expect(obj.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
|
||||||
|
const ob2: any = { a: 1 };
|
||||||
|
observer.observe(ob2);
|
||||||
|
expect(ob2.__owl__.rev).toBe(1);
|
||||||
|
ob2.a = 2;
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(ob2.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
ob2.b = 3;
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(ob2.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
observer.set(ob2, "b", 4);
|
||||||
|
expect(observer.rev).toBe(3);
|
||||||
|
expect(ob2.__owl__.rev).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly handle null or undefined", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj: any = { a: null, b: undefined };
|
||||||
|
|
||||||
|
observer.observe(obj);
|
||||||
|
expect(obj.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
|
||||||
|
obj.a = 3;
|
||||||
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
obj.b = 5;
|
||||||
|
expect(obj.__owl__.rev).toBe(3);
|
||||||
|
|
||||||
|
obj.a = null;
|
||||||
|
obj.b = undefined;
|
||||||
|
expect(obj.__owl__.rev).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can change values in array", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj: any = { arr: [1, 2] };
|
||||||
|
|
||||||
|
observer.observe(obj);
|
||||||
|
expect(obj.arr.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
|
||||||
|
obj.arr[0] = "nope";
|
||||||
|
expect(obj.arr.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
|
||||||
|
observer.set(obj.arr, 0, "yep");
|
||||||
|
expect(obj.arr.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("various object property changes", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj: any = { a: 1 };
|
||||||
|
observer.observe(obj);
|
||||||
|
expect(obj.__owl__.rev).toBe(1);
|
||||||
|
obj.a = 2;
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
// same value again
|
||||||
|
obj.a = 2;
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
obj.a = 3;
|
||||||
|
expect(observer.rev).toBe(3);
|
||||||
|
expect(obj.__owl__.rev).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly observe arrays", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const arr: any = [];
|
||||||
|
observer.observe(arr);
|
||||||
|
expect(arr.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
expect(arr.length).toBe(0);
|
||||||
|
|
||||||
|
arr.push(1);
|
||||||
|
expect(arr.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(arr.length).toBe(1);
|
||||||
|
|
||||||
|
arr.splice(1, 0, "hey");
|
||||||
|
expect(arr.__owl__.rev).toBe(3);
|
||||||
|
expect(observer.rev).toBe(3);
|
||||||
|
expect(arr.length).toBe(2);
|
||||||
|
|
||||||
|
arr.unshift("lindemans");
|
||||||
|
expect(arr.__owl__.rev).toBe(4);
|
||||||
|
|
||||||
|
arr.reverse();
|
||||||
|
expect(arr.__owl__.rev).toBe(5);
|
||||||
|
|
||||||
|
arr.pop();
|
||||||
|
expect(arr.__owl__.rev).toBe(6);
|
||||||
|
|
||||||
|
arr.shift();
|
||||||
|
expect(arr.__owl__.rev).toBe(7);
|
||||||
|
|
||||||
|
arr.sort();
|
||||||
|
expect(arr.__owl__.rev).toBe(8);
|
||||||
|
|
||||||
|
expect(arr).toEqual([1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("object pushed into arrays are observed", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const arr: any = [];
|
||||||
|
observer.observe(arr);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
|
||||||
|
arr.push({ kriek: 5 });
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(arr.__owl__.rev).toBe(2);
|
||||||
|
expect(arr[0].__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
arr[0].kriek = 6;
|
||||||
|
expect(observer.rev).toBe(3);
|
||||||
|
expect(arr.__owl__.rev).toBe(2);
|
||||||
|
expect(arr[0].__owl__.rev).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly observe arrays in object", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const state: any = { arr: [] };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.arr.__owl__.rev).toBe(1);
|
||||||
|
expect(observer.rev).toBe(1);
|
||||||
|
expect(state.arr.length).toBe(0);
|
||||||
|
|
||||||
|
state.arr.push(1);
|
||||||
|
expect(state.arr.__owl__.rev).toBe(2);
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(state.arr.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly observe objects in array", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const state: any = { arr: [{ something: 1 }] };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.arr.__owl__.rev).toBe(1);
|
||||||
|
expect(state.arr[0].__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
state.arr[0].something = 2;
|
||||||
|
expect(state.arr.__owl__.rev).toBe(1);
|
||||||
|
expect(state.arr[0].__owl__.rev).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly observe objects in object", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const state: any = { a: { b: 1 } };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(state.a.__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
state.a.b = 2;
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(state.a.__owl__.rev).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly unobserve objects in object", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const state: any = { a: { b: 1 } };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
const initialA = state.a;
|
||||||
|
expect(initialA.__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
state.a = "Karlsquell";
|
||||||
|
expect(initialA.__owl__).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reobserve new object values", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj: any = { a: 1 };
|
||||||
|
observer.observe(obj);
|
||||||
|
expect(obj.__owl__.rev).toBe(1);
|
||||||
|
obj.a = { b: 2 };
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
|
expect(obj.a.__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
obj.a.b = 3;
|
||||||
|
expect(observer.rev).toBe(3);
|
||||||
|
expect(obj.__owl__.rev).toBe(2);
|
||||||
|
expect(obj.a.__owl__.rev).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deep observe misc changes", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const state: any = { o: { a: 1 }, arr: [1], n: 13 };
|
||||||
|
observer.observe(state);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(state.__owl__.deepRev).toBe(1);
|
||||||
|
|
||||||
|
state.o.a = 2;
|
||||||
|
expect(observer.rev).toBe(2);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(state.__owl__.deepRev).toBe(2);
|
||||||
|
|
||||||
|
state.arr.push(2);
|
||||||
|
expect(state.__owl__.rev).toBe(1);
|
||||||
|
expect(state.__owl__.deepRev).toBe(3);
|
||||||
|
|
||||||
|
state.n = 155;
|
||||||
|
expect(state.__owl__.rev).toBe(2);
|
||||||
|
expect(state.__owl__.deepRev).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("properly handle already observed state", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj1: any = { a: 1 };
|
||||||
|
const obj2: any = { b: 1 };
|
||||||
|
observer.observe(obj1);
|
||||||
|
observer.observe(obj2);
|
||||||
|
expect(obj1.__owl__.rev).toBe(1);
|
||||||
|
expect(obj2.__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
obj1.a = 2;
|
||||||
|
obj2.b = 3;
|
||||||
|
expect(obj1.__owl__.rev).toBe(2);
|
||||||
|
expect(obj2.__owl__.rev).toBe(2);
|
||||||
|
|
||||||
|
obj2.b = obj1;
|
||||||
|
expect(obj1.__owl__.rev).toBe(2);
|
||||||
|
expect(obj2.__owl__.rev).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("accept cycles in observed state", () => {
|
||||||
|
const observer = makeObserver();
|
||||||
|
const obj1: any = {};
|
||||||
|
const obj2: any = { b: obj1, key: 1 };
|
||||||
|
obj1.a = obj2;
|
||||||
|
observer.observe(obj1);
|
||||||
|
expect(obj1.__owl__.rev).toBe(1);
|
||||||
|
expect(obj2.__owl__.rev).toBe(1);
|
||||||
|
|
||||||
|
obj2.key = 3;
|
||||||
|
expect(obj1.__owl__.rev).toBe(1);
|
||||||
|
expect(obj2.__owl__.rev).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
+28
-268
@@ -1,11 +1,11 @@
|
|||||||
import { Store, makeObserver, connect } from "../src/store";
|
import { Component, Env } from "../src/component";
|
||||||
|
import { connect, Store } from "../src/store";
|
||||||
import {
|
import {
|
||||||
nextTick,
|
|
||||||
nextMicroTick,
|
|
||||||
makeTestFixture,
|
makeTestFixture,
|
||||||
makeTestWEnv
|
makeTestWEnv,
|
||||||
|
nextMicroTick,
|
||||||
|
nextTick
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { Env, Component } from "../src/component";
|
|
||||||
|
|
||||||
describe("basic use", () => {
|
describe("basic use", () => {
|
||||||
test("commit a mutation", () => {
|
test("commit a mutation", () => {
|
||||||
@@ -171,260 +171,6 @@ describe("basic use", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("observer", () => {
|
|
||||||
test("properly observe objects", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj: any = {};
|
|
||||||
|
|
||||||
observer.observe(obj);
|
|
||||||
expect(obj.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
|
|
||||||
const ob2: any = { a: 1 };
|
|
||||||
observer.observe(ob2);
|
|
||||||
expect(ob2.__owl__.rev).toBe(1);
|
|
||||||
ob2.a = 2;
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(ob2.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
ob2.b = 3;
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(ob2.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
observer.set(ob2, "b", 4);
|
|
||||||
expect(observer.rev).toBe(3);
|
|
||||||
expect(ob2.__owl__.rev).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly handle null or undefined", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj: any = { a: null, b: undefined };
|
|
||||||
|
|
||||||
observer.observe(obj);
|
|
||||||
expect(obj.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
|
|
||||||
obj.a = 3;
|
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
obj.b = 5;
|
|
||||||
expect(obj.__owl__.rev).toBe(3);
|
|
||||||
|
|
||||||
obj.a = null;
|
|
||||||
obj.b = undefined;
|
|
||||||
expect(obj.__owl__.rev).toBe(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("can change values in array", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj: any = { arr: [1, 2] };
|
|
||||||
|
|
||||||
observer.observe(obj);
|
|
||||||
expect(obj.arr.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
|
|
||||||
obj.arr[0] = "nope";
|
|
||||||
expect(obj.arr.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
|
|
||||||
observer.set(obj.arr, 0, "yep");
|
|
||||||
expect(obj.arr.__owl__.rev).toBe(2);
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("various object property changes", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj: any = { a: 1 };
|
|
||||||
observer.observe(obj);
|
|
||||||
expect(obj.__owl__.rev).toBe(1);
|
|
||||||
obj.a = 2;
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
// same value again
|
|
||||||
obj.a = 2;
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
obj.a = 3;
|
|
||||||
expect(observer.rev).toBe(3);
|
|
||||||
expect(obj.__owl__.rev).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly observe arrays", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const arr: any = [];
|
|
||||||
observer.observe(arr);
|
|
||||||
expect(arr.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
expect(arr.length).toBe(0);
|
|
||||||
|
|
||||||
arr.push(1);
|
|
||||||
expect(arr.__owl__.rev).toBe(2);
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(arr.length).toBe(1);
|
|
||||||
|
|
||||||
arr.splice(1, 0, "hey");
|
|
||||||
expect(arr.__owl__.rev).toBe(3);
|
|
||||||
expect(observer.rev).toBe(3);
|
|
||||||
expect(arr.length).toBe(2);
|
|
||||||
|
|
||||||
arr.unshift("lindemans");
|
|
||||||
expect(arr.__owl__.rev).toBe(4);
|
|
||||||
|
|
||||||
arr.reverse();
|
|
||||||
expect(arr.__owl__.rev).toBe(5);
|
|
||||||
|
|
||||||
arr.pop();
|
|
||||||
expect(arr.__owl__.rev).toBe(6);
|
|
||||||
|
|
||||||
arr.shift();
|
|
||||||
expect(arr.__owl__.rev).toBe(7);
|
|
||||||
|
|
||||||
arr.sort();
|
|
||||||
expect(arr.__owl__.rev).toBe(8);
|
|
||||||
|
|
||||||
expect(arr).toEqual([1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("object pushed into arrays are observed", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const arr: any = [];
|
|
||||||
observer.observe(arr);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
|
|
||||||
arr.push({ kriek: 5 });
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(arr.__owl__.rev).toBe(2);
|
|
||||||
expect(arr[0].__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
arr[0].kriek = 6;
|
|
||||||
expect(observer.rev).toBe(3);
|
|
||||||
expect(arr.__owl__.rev).toBe(2);
|
|
||||||
expect(arr[0].__owl__.rev).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly observe arrays in object", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const state: any = { arr: [] };
|
|
||||||
observer.observe(state);
|
|
||||||
expect(state.arr.__owl__.rev).toBe(1);
|
|
||||||
expect(observer.rev).toBe(1);
|
|
||||||
expect(state.arr.length).toBe(0);
|
|
||||||
|
|
||||||
state.arr.push(1);
|
|
||||||
expect(state.arr.__owl__.rev).toBe(2);
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(state.arr.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly observe objects in array", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const state: any = { arr: [{ something: 1 }] };
|
|
||||||
observer.observe(state);
|
|
||||||
expect(state.arr.__owl__.rev).toBe(1);
|
|
||||||
expect(state.arr[0].__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
state.arr[0].something = 2;
|
|
||||||
expect(state.arr.__owl__.rev).toBe(1);
|
|
||||||
expect(state.arr[0].__owl__.rev).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly observe objects in object", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const state: any = { a: { b: 1 } };
|
|
||||||
observer.observe(state);
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
expect(state.a.__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
state.a.b = 2;
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
expect(state.a.__owl__.rev).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly unobserve objects in object", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const state: any = { a: { b: 1 } };
|
|
||||||
observer.observe(state);
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
const initialA = state.a;
|
|
||||||
expect(initialA.__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
state.a = "Karlsquell";
|
|
||||||
expect(initialA.__owl__).not.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("reobserve new object values", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj: any = { a: 1 };
|
|
||||||
observer.observe(obj);
|
|
||||||
expect(obj.__owl__.rev).toBe(1);
|
|
||||||
obj.a = { b: 2 };
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
|
||||||
expect(obj.a.__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
obj.a.b = 3;
|
|
||||||
expect(observer.rev).toBe(3);
|
|
||||||
expect(obj.__owl__.rev).toBe(2);
|
|
||||||
expect(obj.a.__owl__.rev).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deep observe misc changes", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const state: any = { o: { a: 1 }, arr: [1], n: 13 };
|
|
||||||
observer.observe(state);
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
expect(state.__owl__.deepRev).toBe(1);
|
|
||||||
|
|
||||||
state.o.a = 2;
|
|
||||||
expect(observer.rev).toBe(2);
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
expect(state.__owl__.deepRev).toBe(2);
|
|
||||||
|
|
||||||
state.arr.push(2);
|
|
||||||
expect(state.__owl__.rev).toBe(1);
|
|
||||||
expect(state.__owl__.deepRev).toBe(3);
|
|
||||||
|
|
||||||
state.n = 155;
|
|
||||||
expect(state.__owl__.rev).toBe(2);
|
|
||||||
expect(state.__owl__.deepRev).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("properly handle already observed state", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj1: any = { a: 1 };
|
|
||||||
const obj2: any = { b: 1 };
|
|
||||||
observer.observe(obj1);
|
|
||||||
observer.observe(obj2);
|
|
||||||
expect(obj1.__owl__.rev).toBe(1);
|
|
||||||
expect(obj2.__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
obj1.a = 2;
|
|
||||||
obj2.b = 3;
|
|
||||||
expect(obj1.__owl__.rev).toBe(2);
|
|
||||||
expect(obj2.__owl__.rev).toBe(2);
|
|
||||||
|
|
||||||
obj2.b = obj1;
|
|
||||||
expect(obj1.__owl__.rev).toBe(2);
|
|
||||||
expect(obj2.__owl__.rev).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("accept cycles in observed state", () => {
|
|
||||||
const observer = makeObserver();
|
|
||||||
const obj1: any = {};
|
|
||||||
const obj2: any = { b: obj1, key: 1 };
|
|
||||||
obj1.a = obj2;
|
|
||||||
observer.observe(obj1);
|
|
||||||
expect(obj1.__owl__.rev).toBe(1);
|
|
||||||
expect(obj2.__owl__.rev).toBe(1);
|
|
||||||
|
|
||||||
obj2.key = 3;
|
|
||||||
expect(obj1.__owl__.rev).toBe(1);
|
|
||||||
expect(obj2.__owl__.rev).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("advanced state properties", () => {
|
describe("advanced state properties", () => {
|
||||||
test("state in the store is reference equal after mutation", async () => {
|
test("state in the store is reference equal after mutation", async () => {
|
||||||
const state = {};
|
const state = {};
|
||||||
@@ -845,7 +591,7 @@ describe("connecting a component to store", () => {
|
|||||||
return {
|
return {
|
||||||
selected: state.beers[props.id],
|
selected: state.beers[props.id],
|
||||||
consumed: state.beers[state.consumedID] || null,
|
consumed: state.beers[state.consumedID] || null,
|
||||||
taster: state.taster,
|
taster: state.taster
|
||||||
};
|
};
|
||||||
})(Beer);
|
})(Beer);
|
||||||
|
|
||||||
@@ -866,7 +612,7 @@ describe("connecting a component to store", () => {
|
|||||||
},
|
},
|
||||||
renameBeer({ state }, { beerId, name }) {
|
renameBeer({ state }, { beerId, name }) {
|
||||||
state.beers[beerId].name = name;
|
state.beers[beerId].name = name;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
const state = {
|
const state = {
|
||||||
beers: {
|
beers: {
|
||||||
@@ -880,28 +626,42 @@ describe("connecting a component to store", () => {
|
|||||||
const app = new App(env);
|
const app = new App(env);
|
||||||
|
|
||||||
await app.mount(fixture);
|
await app.mount(fixture);
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
await app.updateState({ beerId: 1 });
|
await app.updateState({ beerId: 1 });
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span><span>selected:jupiler</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
store.commit("renameBeer", { beerId: 1, name: "kwak" });
|
store.commit("renameBeer", { beerId: 1, name: "kwak" });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span><span>selected:kwak</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span><span>selected:kwak</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
store.commit("consume", 1);
|
store.commit("consume", 1);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span><span>selected:kwak</span><span>consumed:kwak</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
await app.updateState({ beerId: 0 });
|
await app.updateState({ beerId: 0 });
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span><span>consumed:kwak</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
store.commit("renameBeer", { beerId: 1, name: "jupiler" });
|
store.commit("renameBeer", { beerId: 1, name: "jupiler" });
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:aaron</span><span>consumed:jupiler</span></div></div>"
|
||||||
|
);
|
||||||
|
|
||||||
store.commit("changeTaster", "matthieu");
|
store.commit("changeTaster", "matthieu");
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe("<div><div><span>taster:matthieu</span><span>consumed:jupiler</span></div></div>");
|
expect(fixture.innerHTML).toBe(
|
||||||
|
"<div><div><span>taster:matthieu</span><span>consumed:jupiler</span></div></div>"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user