mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
d1094f647a
This rev. add a 'set' and 'delete' static functions to the Observer. They allow to set or delete (only for objects) properties on observed objects or arrays. With this, the 'set' functions in Store and Component are no longer necessary, so they have been removed. Closes #138
395 lines
11 KiB
TypeScript
395 lines
11 KiB
TypeScript
import { Observer } from "../src/observer";
|
|
import { nextMicroTick } from "./helpers";
|
|
|
|
describe("observer", () => {
|
|
test("properly observe objects", () => {
|
|
const observer = new Observer();
|
|
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 = new Observer();
|
|
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 = new Observer();
|
|
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 = new Observer();
|
|
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 = new Observer();
|
|
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 = new Observer();
|
|
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(2);
|
|
|
|
arr[0].kriek = 6;
|
|
expect(observer.rev).toBe(3);
|
|
expect(arr.__owl__.rev).toBe(2);
|
|
expect(arr[0].__owl__.rev).toBe(3);
|
|
});
|
|
|
|
test("set new property on observed object", async () => {
|
|
const observer = new Observer();
|
|
observer.notifyCB = jest.fn();
|
|
const state: any = { a: 1 };
|
|
observer.observe(state);
|
|
expect(state.__owl__.rev).toBe(1);
|
|
expect(observer.rev).toBe(1);
|
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
|
|
|
Observer.set(state, "b", 8);
|
|
await nextMicroTick();
|
|
expect(state.__owl__.rev).toBe(2);
|
|
expect(observer.rev).toBe(2);
|
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
|
expect(state.b).toBe(8);
|
|
});
|
|
|
|
test("delete property from observed object", async () => {
|
|
const observer = new Observer();
|
|
observer.notifyCB = jest.fn();
|
|
const state: any = { a: 1, b: 8 };
|
|
observer.observe(state);
|
|
expect(state.__owl__.rev).toBe(1);
|
|
expect(observer.rev).toBe(1);
|
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
|
|
|
Observer.delete(state, "b");
|
|
await nextMicroTick();
|
|
expect(state.__owl__.rev).toBe(2);
|
|
expect(observer.rev).toBe(2);
|
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
|
expect(state).toEqual({ a: 1 });
|
|
});
|
|
|
|
test("set element in observed array", async () => {
|
|
const observer = new Observer();
|
|
observer.notifyCB = jest.fn();
|
|
const state: any = ["a"];
|
|
observer.observe(state);
|
|
expect(state.__owl__.rev).toBe(1);
|
|
expect(observer.rev).toBe(1);
|
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
|
|
|
Observer.set(state, 1, "b");
|
|
await nextMicroTick();
|
|
expect(state.__owl__.rev).toBe(2);
|
|
expect(observer.rev).toBe(2);
|
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
|
expect(state).toEqual(["a", "b"]);
|
|
});
|
|
|
|
test("properly observe arrays in object", () => {
|
|
const observer = new Observer();
|
|
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 = new Observer();
|
|
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 = new Observer();
|
|
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("reobserve new object values", () => {
|
|
const observer = new Observer();
|
|
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);
|
|
|
|
// we start at 2 because it is the new observer rev number
|
|
expect(obj.a.__owl__.rev).toBe(2);
|
|
|
|
obj.a.b = 3;
|
|
expect(observer.rev).toBe(3);
|
|
expect(obj.__owl__.rev).toBe(2);
|
|
expect(obj.a.__owl__.rev).toBe(3);
|
|
});
|
|
|
|
test("deep observe misc changes", () => {
|
|
const observer = new Observer();
|
|
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 = new Observer();
|
|
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("can set a property more than once", () => {
|
|
const observer = new Observer();
|
|
const obj: any = {};
|
|
|
|
observer.observe(obj);
|
|
expect(obj.__owl__.rev).toBe(1);
|
|
expect(observer.rev).toBe(1);
|
|
expect(obj.__owl__.deepRev).toBe(1);
|
|
|
|
observer.set(obj, "aku", "always finds annoying problems");
|
|
expect(observer.rev).toBe(2);
|
|
expect(obj.__owl__.rev).toBe(2);
|
|
expect(obj.__owl__.deepRev).toBe(2);
|
|
|
|
observer.set(obj, "aku", "always finds good problems");
|
|
expect(observer.rev).toBe(3);
|
|
expect(obj.__owl__.rev).toBe(3);
|
|
expect(obj.__owl__.deepRev).toBe(3);
|
|
});
|
|
|
|
test("properly handle swapping elements", () => {
|
|
const observer = new Observer();
|
|
const obj: any = { a: { arr: [] }, b: 1 };
|
|
observer.observe(obj);
|
|
|
|
// swap a and b
|
|
const b = obj.b;
|
|
obj.b = obj.a;
|
|
obj.a = b;
|
|
expect(observer.rev).toBe(3);
|
|
|
|
// push something into array to make sure it works
|
|
obj.b.arr.push("blanche");
|
|
expect(observer.rev).toBe(4);
|
|
});
|
|
|
|
test("properly handle assigning observed obj containing array", () => {
|
|
const observer = new Observer();
|
|
const obj: any = { a: { arr: [], val: "test" } };
|
|
observer.observe(obj);
|
|
|
|
obj.a = { ...obj.a, val: "test2" };
|
|
expect(observer.rev).toBe(2);
|
|
|
|
// push something into array to make sure it works
|
|
obj.a.arr.push("blanche");
|
|
expect(observer.rev).toBe(3);
|
|
});
|
|
|
|
test("accept cycles in observed state", () => {
|
|
const observer = new Observer();
|
|
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);
|
|
});
|
|
|
|
test("call callback when state is changed", async () => {
|
|
const observer = new Observer();
|
|
observer.notifyCB = jest.fn();
|
|
const obj: any = { a: 1, b: { c: 2 }, d: [{ e: 3 }], f: 4 };
|
|
|
|
observer.observe(obj);
|
|
expect(observer.notifyCB).toBeCalledTimes(0);
|
|
|
|
obj.a = 2;
|
|
await nextMicroTick();
|
|
expect(observer.notifyCB).toBeCalledTimes(1);
|
|
|
|
obj.b.c = 3;
|
|
await nextMicroTick();
|
|
expect(observer.notifyCB).toBeCalledTimes(2);
|
|
|
|
obj.d[0].e = 5;
|
|
await nextMicroTick();
|
|
expect(observer.notifyCB).toBeCalledTimes(3);
|
|
|
|
obj.a = 111;
|
|
obj.f = 222;
|
|
await nextMicroTick();
|
|
expect(observer.notifyCB).toBeCalledTimes(4);
|
|
});
|
|
|
|
test("throw error when state is mutated in object if allowMutation=false", async () => {
|
|
const observer = new Observer();
|
|
observer.allowMutations = false;
|
|
const obj: any = { a: 1 };
|
|
observer.observe(obj);
|
|
|
|
expect(() => {
|
|
obj.a = 2;
|
|
}).toThrow('Observed state cannot be changed here! (key: "a", val: "2")');
|
|
});
|
|
|
|
test("throw error when state is mutated in array if allowMutation=false", async () => {
|
|
const observer = new Observer();
|
|
observer.allowMutations = false;
|
|
const obj: any = { a: [1] };
|
|
observer.observe(obj);
|
|
|
|
expect(() => {
|
|
obj.a.push(2);
|
|
}).toThrow("Array cannot be changed here");
|
|
});
|
|
});
|