import { Component, mount, onWillRender, onWillStart, onWillUpdateProps, useState } from "../src"; import { batched, reactive } from "../src/reactivity"; import { xml } from "../src/tags"; import { makeDeferred, makeTestFixture, nextMicroTick, nextTick, snapshotEverything, useLogLifecycle, } from "./helpers"; function createReactive(value: any, observer: any = () => {}) { return reactive(value, observer); } describe("Reactivity", () => { test("can read", async () => { const state = createReactive({ a: 1 }); expect(state.a).toBe(1); }); test("can create new keys", () => { const state = createReactive({}); state.a = 1; expect(state.a).toBe(1); }); test("can update", () => { const state = createReactive({ a: 1 }); state.a = 2; expect(state.a).toBe(2); }); test("can delete existing keys", () => { const state = createReactive({ a: 1 }); delete state.a; expect(state).not.toHaveProperty("a"); }); test("act like an object", () => { const state = createReactive({ a: 1 }); expect(Object.keys(state)).toEqual(["a"]); expect(Object.values(state)).toEqual([1]); expect(typeof state).toBe("object"); }); test("act like an array", () => { const state = createReactive(["a", "b"]); expect(state.length).toBe(2); expect(state).toEqual(["a", "b"]); expect(typeof state).toBe("object"); expect(Array.isArray(state)).toBe(true); }); test("Throw error if value is not proxifiable", () => { expect(() => createReactive(1)).toThrow("Cannot make the given value reactive"); }); test("callback is called when changing an observed property 1", async () => { let n = 0; const state = createReactive({ a: 1 }, () => n++); state.a = 2; expect(n).toBe(0); // key has not be read yet state.a = state.a + 5; // key is read and then modified expect(n).toBe(1); }); test("callback is called when changing an observed property 2", async () => { let n = 0; const state = createReactive({ a: { k: 1 } }, () => n++); state.a.k = state.a.k + 1; expect(n).toBe(1); state.k = 2; // observer has been interested specifically to key k of a! expect(n).toBe(1); }); test("reactive from object with a getter 1", async () => { let n = 0; let value = 1; const state = createReactive( { get a() { return value; }, set a(val) { value = val; }, }, () => n++ ); state.a = state.a + 4; await nextMicroTick(); expect(n).toBe(1); }); test("reactive from object with a getter 2", async () => { let n = 0; let value = { b: 1 }; const state = createReactive( { get a() { return value; }, }, () => n++ ); expect(state.a.b).toBe(1); state.a.b = 2; await nextMicroTick(); expect(n).toBe(1); }); test("reactive from object with a getter 3", async () => { let n = 0; const values: { b: number }[] = createReactive([]); function createValue() { const o = { b: values.length }; values.push(o); return o; } const reactive = createReactive( { get a() { return createValue(); }, }, () => n++ ); for (let i = 0; i < 10; i++) { expect(reactive.a.b).toEqual(i); } expect(n).toBe(0); values[0].b = 3; expect(n).toBe(1); // !!! reactives for each object in values are still there !!! values[0].b = 4; expect(n).toBe(1); // reactives for each object in values were cleaned up by the previous write }); test("Operator 'in' causes key's presence to be observed", async () => { let n = 0; const state = createReactive({}, () => n++); "a" in state; state.a = 2; expect(n).toBe(1); "a" in state; state.a = 3; // Write on existing property shouldn't notify expect(n).toBe(1); "a" in state; delete state.a; expect(n).toBe(2); }); test.skip("hasOwnProperty causes the key's presence to be observed", async () => { let n = 0; const state = createReactive({}, () => n++); Object.hasOwnProperty.call(state, "a"); state.a = 2; expect(n).toBe(1); Object.hasOwnProperty.call(state, "a"); state.a = 3; expect(n).toBe(1); Object.hasOwnProperty.call(state, "a"); delete state.a; expect(n).toBe(2); }); test("batched: callback is called after batch of operation", async () => { let n = 0; const state = createReactive( { a: 1, b: 2 }, batched(() => n++) ); state.a = 2; expect(n).toBe(0); await nextMicroTick(); expect(n).toBe(0); // key has not be read yet state.a = state.a + 5; // key is read and then modified expect(n).toBe(0); state.b = state.b + 5; // key is read and then modified expect(n).toBe(0); await nextMicroTick(); expect(n).toBe(1); // two operations but only one notification }); test("setting property to same value does not trigger callback", async () => { let n = 0; const state = createReactive({ a: 1 }, () => n++); state.a = state.a + 5; // read and modifies property a to have value 6 expect(n).toBe(1); state.a = 6; // same value expect(n).toBe(1); }); test("observe cycles", async () => { const a = { a: {} }; a.a = a; let n = 0; const state = createReactive(a, () => n++); state.k; state.k = 2; expect(n).toBe(1); delete state.l; expect(n).toBe(1); state.k; delete state.k; expect(n).toBe(2); state.a = 1; expect(n).toBe(2); state.a = state.a + 5; expect(n).toBe(3); }); test("equality", async () => { const a = { a: {}, b: 1 }; a.a = a; let n = 0; const state = createReactive(a, () => n++); expect(state).toBe(state.a); expect(n).toBe(0); (state.b = state.b + 1), expect(n).toBe(1); expect(state).toBe(state.a); }); test("two observers for same source", async () => { let m = 0; let n = 0; const obj = { a: 1 } as any; const state = createReactive(obj, () => m++); const state2 = createReactive(obj, () => n++); obj.new = 2; expect(m).toBe(0); expect(n).toBe(0); state.new = 2; // already exists! expect(m).toBe(0); expect(n).toBe(0); state.veryNew; state2.veryNew; state.veryNew = 2; expect(m).toBe(1); expect(n).toBe(1); state.a = state.a + 5; expect(m).toBe(2); expect(n).toBe(1); state.a; state2.a = state2.a + 5; expect(m).toBe(3); expect(n).toBe(2); state.veryNew; state2.veryNew; delete state2.veryNew; expect(m).toBe(4); expect(n).toBe(3); }); test("create reactive from another", async () => { let n = 0; const state = createReactive({ a: 1 }); const state2 = createReactive(state, () => n++); state2.a = state2.a + 5; expect(n).toBe(1); state2.a; state.a = 2; expect(n).toBe(2); }); test("create reactive from another 2", async () => { let n = 0; const state = createReactive({ a: 1 }); const state2 = createReactive(state, () => n++); state.a = state2.a + 5; expect(n).toBe(1); state2.a = state2.a + 5; expect(n).toBe(2); }); test("create reactive from another 3", async () => { let n = 0; const state = createReactive({ a: 1 }); const state2 = createReactive(state, () => n++); state.a = state.a + 5; expect(n).toBe(0); // state2.a was not yet read state2.a = state2.a + 5; state2.a; expect(n).toBe(1); // state2.a has been read and is now observed state.a = state.a + 5; expect(n).toBe(2); }); test("throws on primitive values", () => { expect(() => createReactive(1)).toThrowError(); expect(() => createReactive("asf")).toThrowError(); expect(() => createReactive(true)).toThrowError(); expect(() => createReactive(null)).toThrowError(); expect(() => createReactive(undefined)).toThrowError(); }); test("throws on dates", () => { const date = new Date(); expect(() => createReactive(date)).toThrow("Cannot make the given value reactive"); }); test("can observe object with some key set to null", async () => { let n = 0; const state = createReactive({ a: { b: null } } as any, () => n++); expect(n).toBe(0); state.a.b = Boolean(state.a.b); expect(n).toBe(1); }); test("can reobserve object with some key set to null", async () => { let n = 0; const fn = () => n++; const state = createReactive({ a: { b: null } } as any, fn); const state2 = createReactive(state, fn); expect(state2).toBe(state); expect(state2).toEqual(state); expect(n).toBe(0); state.a.b = Boolean(state.a.b); expect(n).toBe(1); }); test("contains initial values", () => { const state = createReactive({ a: 1, b: 2 }); expect(state.a).toBe(1); expect(state.b).toBe(2); expect((state as any).c).toBeUndefined(); }); test("detect object value changes", async () => { let n = 0; const state = createReactive({ a: 1 }, () => n++) as any; expect(n).toBe(0); state.a = state.a + 5; expect(n).toBe(1); state.b = state.b + 5; expect(n).toBe(2); state.a; state.b; state.a = null; state.b = undefined; expect(n).toBe(3); expect(state).toEqual({ a: null, b: undefined }); }); test("properly handle dates", async () => { const date = new Date(); let n = 0; const state = createReactive({ date }, () => n++); expect(typeof state.date.getFullYear()).toBe("number"); expect(state.date).toBe(date); state.date = new Date(); expect(n).toBe(1); expect(state.date).not.toBe(date); }); test("properly handle promise", async () => { let resolved = false; let n = 0; const state = createReactive({ prom: Promise.resolve() }, () => n++); expect(state.prom).toBeInstanceOf(Promise); state.prom.then(() => (resolved = true)); expect(n).toBe(0); expect(resolved).toBe(false); await Promise.resolve(); expect(resolved).toBe(true); expect(n).toBe(0); }); test("can observe value change in array in an object", async () => { let n = 0; const state = createReactive({ arr: [1, 2] }, () => n++) as any; expect(Array.isArray(state.arr)).toBe(true); expect(n).toBe(0); state.arr[0] = state.arr[0] + "nope"; expect(n).toBe(1); expect(state.arr[0]).toBe("1nope"); expect(state.arr).toEqual(["1nope", 2]); }); test("can observe: changing array in object to another array", async () => { let n = 0; const state = createReactive({ arr: [1, 2] }, () => n++) as any; expect(Array.isArray(state.arr)).toBe(true); expect(n).toBe(0); state.arr = [2, 1]; expect(n).toBe(1); expect(state.arr[0]).toBe(2); expect(state.arr).toEqual([2, 1]); }); test("getting the same property twice returns the same object", () => { const state = createReactive({ a: { b: 1 } }); const a1 = state.a; const a2 = state.a; expect(a1).toBe(a2); }); test("various object property changes", async () => { let n = 0; const state = createReactive({ a: 1 }, () => n++) as any; expect(n).toBe(0); state.a = state.a + 2; expect(n).toBe(1); state.a; // same value again: no notification state.a = 3; expect(n).toBe(1); state.a = 4; expect(n).toBe(2); }); test("properly observe arrays", async () => { let n = 0; const state = createReactive([], () => n++) as any; expect(Array.isArray(state)).toBe(true); expect(state.length).toBe(0); expect(n).toBe(0); state.push(1); expect(n).toBe(1); expect(state.length).toBe(1); expect(state).toEqual([1]); state.splice(1, 0, "hey"); expect(n).toBe(2); expect(state).toEqual([1, "hey"]); expect(state.length).toBe(2); // clear all observations caused by previous expects state[0] = 2; expect(n).toBe(3); state.unshift("lindemans"); // unshift generates the following sequence of operations: (observed keys in brackets) // - read 'unshift' => { unshift } // - read 'length' => { unshift , length } // - hasProperty '1' => { unshift , length, [KEYCHANGES] } // - read '1' => { unshift , length, 1 } // - write "hey" on '2' => notification for key creation, {} // - hasProperty '0' => { [KEYCHANGES] } // - read '0' => { 0, [KEYCHANGES] } // - write "2" on '1' => not observing '1', no notification // - write "lindemans" on '0' => notification, stop observing {} // - write 3 on 'length' => not observing 'length', no notification expect(n).toBe(5); expect(state).toEqual(["lindemans", 2, "hey"]); expect(state.length).toBe(3); // clear all observations caused by previous expects state[1] = 3; expect(n).toBe(6); state.reverse(); // Reverse will generate floor(length/2) notifications because it swaps elements pair-wise expect(n).toBe(7); expect(state).toEqual(["hey", 3, "lindemans"]); expect(state.length).toBe(3); state.pop(); // reads '2', deletes '2', sets length. Only delete triggers a notification expect(n).toBe(8); expect(state).toEqual(["hey", 3]); expect(state.length).toBe(2); state.shift(); // reads '0', reads '1', sets '0', sets length. Only set '0' triggers a notification expect(n).toBe(9); expect(state).toEqual([3]); expect(state.length).toBe(1); }); test("object pushed into arrays are observed", async () => { let n = 0; const arr: any = createReactive([], () => n++); arr.push({ kriek: 5 }); expect(n).toBe(1); arr[0].kriek = 6; expect(n).toBe(1); arr[0].kriek = arr[0].kriek + 6; expect(n).toBe(2); }); test("set new property on observed object", async () => { let n = 0; let keys: string[] = []; const notify = () => { n++; keys.splice(0); keys.push(...Object.keys(state)); }; const state = createReactive({}, notify) as any; Object.keys(state); expect(n).toBe(0); state.b = 8; expect(n).toBe(1); expect(state.b).toBe(8); expect(keys).toEqual(["b"]); }); test("set new property object when key changes are not observed", async () => { let n = 0; const notify = () => n++; const state = createReactive({ a: 1 }, notify) as any; state.a; expect(n).toBe(0); state.b = 8; expect(n).toBe(0); // Not observing key changes: shouldn't get notified expect(state.b).toBe(8); expect(state).toEqual({ a: 1, b: 8 }); }); test("delete property from observed object", async () => { let n = 0; const state = createReactive({ a: 1, b: 8 }, () => n++) as any; Object.keys(state); expect(n).toBe(0); delete state.b; expect(n).toBe(1); expect(state).toEqual({ a: 1 }); }); test("delete property from observed object 2", async () => { let n = 0; const observer = () => n++; const obj = { a: { b: 1 } }; const state = createReactive(obj.a, observer) as any; const state2 = createReactive(obj, observer) as any; expect(state2.a).toBe(state); expect(n).toBe(0); Object.keys(state2); delete state2.a; expect(n).toBe(1); Object.keys(state); state.new = 2; expect(n).toBe(2); }); test("set element in observed array", async () => { let n = 0; const arr = createReactive(["a"], () => n++); arr[1]; arr[1] = "b"; expect(n).toBe(1); expect(arr).toEqual(["a", "b"]); }); test("properly observe arrays in object", async () => { let n = 0; const state = createReactive({ arr: [] }, () => n++) as any; expect(state.arr.length).toBe(0); expect(n).toBe(0); state.arr.push(1); expect(n).toBe(1); expect(state.arr.length).toBe(1); }); test("properly observe objects in array", async () => { let n = 0; const state = createReactive({ arr: [{ something: 1 }] }, () => n++) as any; expect(n).toBe(0); state.arr[0].something = state.arr[0].something + 1; expect(n).toBe(1); expect(state.arr[0].something).toBe(2); }); test("properly observe objects in object", async () => { let n = 0; const state = createReactive({ a: { b: 1 } }, () => n++) as any; expect(n).toBe(0); state.a.b = state.a.b + 2; expect(n).toBe(1); }); test("Observing the same object through the same reactive preserves referential equality", async () => { const o = {} as any; o.o = o; const state = createReactive(o); expect(state.o).toBe(state); expect(state.o.o).toBe(state); }); test("reobserve new object values", async () => { let n = 0; const state = createReactive({ a: 1 }, () => n++) as any; expect(n).toBe(0); state.a++; state.a; expect(n).toBe(1); state.a = { b: 2 }; expect(n).toBe(2); state.a.b = state.a.b + 3; expect(n).toBe(3); }); test("deep observe misc changes", async () => { let n = 0; const state = createReactive({ o: { a: 1 }, arr: [1], n: 13 }, () => n++) as any; expect(n).toBe(0); state.o.a = state.o.a + 2; expect(n).toBe(1); state.arr.push(2); expect(n).toBe(2); state.n = 155; expect(n).toBe(2); state.n = state.n + 1; expect(n).toBe(3); }); test("properly handle already observed object", async () => { let n1 = 0; let n2 = 0; const obj1 = createReactive({ a: 1 }, () => n1++) as any; const obj2 = createReactive({ b: 1 }, () => n2++) as any; obj1.a = obj1.a + 2; obj2.b = obj2.b + 3; expect(n1).toBe(1); expect(n2).toBe(1); obj2.b; obj2.b = obj1; expect(n1).toBe(1); expect(n2).toBe(2); obj1.a; obj1.a = 33; expect(n1).toBe(2); expect(n2).toBe(2); obj1.a; obj2.b.a = obj2.b.a + 2; expect(n1).toBe(3); expect(n2).toBe(3); }); test("properly handle already observed object in observed object", async () => { let n1 = 0; let n2 = 0; const obj1 = createReactive({ a: { c: 2 } }, () => n1++) as any; const obj2 = createReactive({ b: 1 }, () => n2++) as any; obj2.c; obj2.c = obj1; expect(n1).toBe(0); expect(n2).toBe(1); obj1.a.c = obj1.a.c + 33; obj1.a.c; expect(n1).toBe(1); expect(n2).toBe(1); obj2.c.a.c = obj2.c.a.c + 3; expect(n1).toBe(2); expect(n2).toBe(2); }); test("can reobserve object", async () => { let n1 = 0; let n2 = 0; const state = createReactive({ a: 0 }, () => n1++) as any; state.a = state.a + 1; expect(n1).toBe(1); expect(n2).toBe(0); const state2 = createReactive(state, () => n2++) as any; expect(state).toEqual(state2); state2.a = 2; expect(n1).toBe(2); expect(n2).toBe(1); }); test("can reobserve nested properties in object", async () => { let n1 = 0; let n2 = 0; const state = createReactive({ a: [{ b: 1 }] }, () => n1++) as any; const state2 = createReactive(state, () => n2++) as any; state.a[0].b = state.a[0].b + 2; expect(n1).toBe(1); expect(n2).toBe(0); state.c; state2.c; state2.c = 2; expect(n1).toBe(2); expect(n2).toBe(1); }); test("rereading some property again give exactly same result", () => { const state = createReactive({ a: { b: 1 } }); const obj1 = state.a; const obj2 = state.a; expect(obj1).toBe(obj2); }); test("can reobserve new properties in object", async () => { let n1 = 0; let n2 = 0; const state = createReactive({ a: [{ b: 1 }] }, () => n1++) as any; createReactive(state, () => n2++) as any; state.a[0].b = { c: 1 }; expect(n1).toBe(0); expect(n2).toBe(0); state.a[0].b.c = state.a[0].b.c + 2; expect(n1).toBe(1); expect(n2).toBe(0); }); test("can observe sub property of observed object", async () => { let n1 = 0; let n2 = 0; const state = createReactive({ a: { b: 1 }, c: 1 }, () => n1++) as any; const state2 = createReactive(state.a, () => n2++) as any; state.a.b = state.a.b + 2; expect(n1).toBe(1); expect(n2).toBe(0); state.l; state.l = 2; expect(n1).toBe(2); expect(n2).toBe(0); state.a.k; state2.k; state.a.k = 3; expect(n1).toBe(3); expect(n2).toBe(1); state.c = 14; expect(n1).toBe(3); expect(n2).toBe(1); state.a.b; state2.b = state2.b + 3; expect(n1).toBe(4); expect(n2).toBe(2); }); test("can set a property more than once", async () => { let n = 0; const state = createReactive({}, () => n++) as any; state.aky = state.aku; expect(n).toBe(0); state.aku = "always finds annoying problems"; expect(n).toBe(1); state.aku; state.aku = "always finds good problems"; expect(n).toBe(2); }); test("properly handle swapping elements", async () => { let n = 0; const state = createReactive({ a: { arr: [] }, b: 1 }, () => n++) as any; // swap a and b const b = state.b; state.b = state.a; state.a = b; expect(n).toBe(1); // push something into array to make sure it works state.b.arr.push("blanche"); // push reads the length property and as such subscribes to the change it is about to cause expect(n).toBe(2); }); test("properly handle assigning object containing array to reactive", async () => { let n = 0; const state = createReactive({ a: { arr: [], val: "test" } }, () => n++) as any; expect(n).toBe(0); state.a = { ...state.a, val: "test2" }; expect(n).toBe(1); // push something into array to make sure it works state.a.arr.push("blanche"); expect(n).toBe(2); }); test.skip("accept cycles in observed object", async () => { // ??? let n = 0; let obj1: any = {}; let obj2: any = { b: obj1, key: 1 }; obj1.a = obj2; obj1 = createReactive(obj1, () => n++) as any; obj2 = obj1.a; expect(n).toBe(0); obj1.key = 3; expect(n).toBe(1); }); test("call callback when reactive is changed", async () => { let n = 0; const state: any = createReactive({ a: 1, b: { c: 2 }, d: [{ e: 3 }], f: 4 }, () => n++); expect(n).toBe(0); state.a = state.a + 2; state.a; expect(n).toBe(1); state.b.c = state.b.c + 3; expect(n).toBe(2); state.d[0].e = state.d[0].e + 5; expect(n).toBe(3); state.a; state.f; state.a = 111; state.f = 222; expect(n).toBe(4); }); // test("can unobserve a value", async () => { // let n = 0; // const cb = () => n++; // const unregisterObserver = registerObserver(cb); // const state = createReactive({ a: 1 }, cb); // state.a = state.a + 3; // await nextMicroTick(); // expect(n).toBe(1); // unregisterObserver(); // state.a = 4; // await nextMicroTick(); // expect(n).toBe(1); // }); test("reactive inside other reactive", async () => { let n1 = 0; let n2 = 0; const inner = createReactive({ a: 1 }, () => n1++); const outer = createReactive({ b: inner }, () => n2++); expect(n1).toBe(0); expect(n2).toBe(0); outer.b.a = outer.b.a + 2; expect(n1).toBe(0); expect(n2).toBe(1); }); test("reactive inside other reactive, variant", async () => { let n1 = 0; let n2 = 0; const inner = createReactive({ a: 1 }, () => n1++); const outer = createReactive({ b: inner, c: 0 }, () => n2++); expect(n1).toBe(0); expect(n2).toBe(0); inner.a = inner.a + 2; expect(n1).toBe(1); expect(n2).toBe(0); outer.c = outer.c + 3; expect(n1).toBe(1); expect(n2).toBe(1); }); test("reactive inside other reactive, variant 2", async () => { let n1 = 0; let n2 = 0; let n3 = 0; const obj1 = createReactive({ a: 1 }, () => n1++); const obj2 = createReactive({ b: {} }, () => n2++); const obj3 = createReactive({ c: {} }, () => n3++); obj2.b = obj2.b; obj2.b; obj3.c = obj3.c; obj3.c; expect(n1).toBe(0); expect(n2).toBe(1); expect(n3).toBe(1); obj2.b = obj1; obj2.b; obj3.c = obj1; obj3.c; expect(n1).toBe(0); expect(n2).toBe(2); expect(n3).toBe(2); obj1.a = obj1.a + 2; obj1.a; expect(n1).toBe(1); expect(n2).toBe(2); expect(n3).toBe(2); obj2.b.a = obj2.b.a + 1; expect(n1).toBe(2); expect(n2).toBe(3); expect(n3).toBe(2); }); // test("notification is not done after unregistration", async () => { // let n = 0; // const observer = () => n++; // const unregisterObserver = registerObserver(observer); // const state = atom({ a: 1 } as any, observer); // state.a = state.a; // await nextMicroTick(); // expect(n).toBe(0); // unregisterObserver(); // state.a = { b: 2 }; // await nextMicroTick(); // expect(n).toBe(0); // state.a.b = state.a.b + 3; // await nextMicroTick(); // expect(n).toBe(0); // }); test("don't react to changes in subobject that has been deleted", async () => { let n = 0; const a = { k: {} } as any; const state = createReactive(a, () => n++); state.k.l; state.k.l = 1; expect(n).toBe(1); const kVal = state.k; delete state.k; expect(n).toBe(2); kVal.l = 2; expect(n).toBe(2); // kVal must no longer be observed }); test("don't react to changes in subobject that has been deleted", async () => { let n = 0; const b = {} as any; const a = { k: b } as any; const observer = () => n++; const state2 = createReactive(b, observer); const state = createReactive(a, observer); state.c = 1; state.k.d; state.k.d = 2; state.k; expect(n).toBe(1); delete state.k; expect(n).toBe(2); state2.e = 3; expect(n).toBe(2); }); test("don't react to changes in subobject that has been deleted 3", async () => { let n = 0; const b = {} as any; const a = { k: b } as any; const observer = () => n++; const state = createReactive(a, observer); const state2 = createReactive(b, observer); state.c = 1; state.k.d; state.k.d = 2; state.k.d; expect(n).toBe(1); delete state.k; expect(n).toBe(2); state2.e = 3; expect(n).toBe(2); }); test("don't react to changes in subobject that has been deleted 4", async () => { let n = 0; const a = { k: {} } as any; a.k = a; const state = createReactive(a, () => n++); Object.keys(state); state.b = 1; expect(n).toBe(1); Object.keys(state); delete state.k; expect(n).toBe(2); state.c = 2; expect(n).toBe(2); }); test("don't react to changes in subobject that has been replaced", async () => { let n = 0; const a = { k: { n: 1 } } as any; const state = createReactive(a, () => n++); const kVal = state.k; // read k state.k = { n: state.k.n + 1 }; await nextMicroTick(); expect(n).toBe(1); expect(state.k).toEqual({ n: 2 }); kVal.n = 3; await nextMicroTick(); expect(n).toBe(1); expect(state.k).toEqual({ n: 2 }); }); }); describe("Reactivity: useState", () => { let fixture: HTMLElement; snapshotEverything(); beforeEach(() => { fixture = makeTestFixture(); }); /** * A context can be defined as a reactive with a default observer. * It can be exposed and share by multiple components or other objects * (via useState for instance) */ test("very simple use, with initial value", async () => { const testContext = createReactive({ value: 123 }); class Comp extends Component { static template = xml`
1a
1a
1a
2b