import { describe, expect, it, vi } from "vitest"; import { computed, effect, reactive } from "../vueMappings"; describe("reactivity/computed", () => { it("should return updated value", () => { const value = reactive<{ foo?: number }>({}); const cValue = computed(() => value.foo); expect(cValue.value).toBe(undefined); value.foo = 1; expect(cValue.value).toBe(1); }); it("should compute lazily", () => { const value = reactive<{ foo?: number }>({}); const getter = vi.fn(() => value.foo); const cValue = computed(getter); // lazy expect(getter).not.toHaveBeenCalled(); expect(cValue.value).toBe(undefined); expect(getter).toHaveBeenCalledTimes(1); // should not compute again cValue.value; expect(getter).toHaveBeenCalledTimes(1); // should not compute until needed value.foo = 1; expect(getter).toHaveBeenCalledTimes(1); // now it should compute expect(cValue.value).toBe(1); expect(getter).toHaveBeenCalledTimes(2); // should not compute again cValue.value; expect(getter).toHaveBeenCalledTimes(2); }); it("should trigger effect", () => { const value = reactive<{ foo?: number }>({}); const cValue = computed(() => value.foo); let dummy; effect(() => { dummy = cValue.value; }); expect(dummy).toBe(undefined); value.foo = 1; expect(dummy).toBe(1); }); it("should work when chained", () => { const value = reactive({ foo: 0 }); const c1 = computed(() => value.foo); const c2 = computed(() => c1.value + 1); expect(c2.value).toBe(1); expect(c1.value).toBe(0); value.foo++; expect(c2.value).toBe(2); expect(c1.value).toBe(1); }); it("should trigger effect when chained", () => { const value = reactive({ foo: 0 }); const getter1 = vi.fn(() => value.foo); const getter2 = vi.fn(() => { return c1.value + 1; }); const c1 = computed(getter1); const c2 = computed(getter2); let dummy; effect(() => { dummy = c2.value; }); expect(dummy).toBe(1); expect(getter1).toHaveBeenCalledTimes(1); expect(getter2).toHaveBeenCalledTimes(1); value.foo++; expect(dummy).toBe(2); // should not result in duplicate calls expect(getter1).toHaveBeenCalledTimes(2); expect(getter2).toHaveBeenCalledTimes(2); }); it("should trigger effect when chained (mixed invocations)", () => { const value = reactive({ foo: 0 }); const getter1 = vi.fn(() => value.foo); const getter2 = vi.fn(() => { return c1.value + 1; }); const c1 = computed(getter1); const c2 = computed(getter2); let dummy; effect(() => { dummy = c1.value + c2.value; }); expect(dummy).toBe(1); expect(getter1).toHaveBeenCalledTimes(1); expect(getter2).toHaveBeenCalledTimes(1); value.foo++; expect(dummy).toBe(3); // should not result in duplicate calls expect(getter1).toHaveBeenCalledTimes(2); expect(getter2).toHaveBeenCalledTimes(2); }); // it("should no longer update when stopped", () => { // const value = reactive<{ foo?: number }>({}); // const cValue = computed(() => value.foo); // let dummy; // effect(() => { // dummy = cValue.value; // }); // expect(dummy).toBe(undefined); // value.foo = 1; // expect(dummy).toBe(1); // cValue.effect.stop(); // value.foo = 2; // expect(dummy).toBe(1); // }); // it("should support setter", () => { // const n = ref(1); // const plusOne = computed({ // get: () => n.value + 1, // set: (val) => { // n.value = val - 1; // }, // }); // expect(plusOne.value).toBe(2); // n.value++; // expect(plusOne.value).toBe(3); // plusOne.value = 0; // expect(n.value).toBe(-1); // }); // it("should trigger effect w/ setter", () => { // const n = ref(1); // const plusOne = computed({ // get: () => n.value + 1, // set: (val) => { // n.value = val - 1; // }, // }); // let dummy; // effect(() => { // dummy = n.value; // }); // expect(dummy).toBe(1); // plusOne.value = 0; // expect(dummy).toBe(-1); // }); // #5720 // it("should invalidate before non-computed effects", () => { // let plusOneValues: number[] = []; // const n = ref(0); // const plusOne = computed(() => n.value + 1); // effect(() => { // n.value; // plusOneValues.push(plusOne.value); // }); // // access plusOne, causing it to be non-dirty // plusOne.value; // // mutate n // n.value++; // // on the 2nd run, plusOne.value should have already updated. // expect(plusOneValues).toMatchObject([1, 2, 2]); // }); // it("should warn if trying to set a readonly computed", () => { // const n = ref(1); // const plusOne = computed(() => n.value + 1); // (plusOne as WritableComputedRef).value++; // Type cast to prevent TS from preventing the error // expect( // "Write operation failed: computed value is readonly" // ).toHaveBeenWarnedLast(); // }); // // it("should be readonly", () => { // let a = { a: 1 }; // const x = computed(() => a); // expect(isReadonly(x)).toBe(true); // expect(isReadonly(x.value)).toBe(false); // expect(isReadonly(x.value.a)).toBe(false); // const z = computed({ // get() { // return a; // }, // set(v) { // a = v; // }, // }); // expect(isReadonly(z)).toBe(false); // expect(isReadonly(z.value.a)).toBe(false); // }); // it("should expose value when stopped", () => { // const x = computed(() => 1); // x.effect.stop(); // expect(x.value).toBe(1); // }); // it("debug: onTrack", () => { // let events: DebuggerEvent[] = []; // const onTrack = vi.fn((e: DebuggerEvent) => { // events.push(e); // }); // const obj = reactive({ foo: 1, bar: 2 }); // const c = computed(() => (obj.foo, "bar" in obj, Object.keys(obj)), { // onTrack, // }); // expect(c.value).toEqual(["foo", "bar"]); // expect(onTrack).toHaveBeenCalledTimes(3); // expect(events).toEqual([ // { // effect: c.effect, // target: toRaw(obj), // type: TrackOpTypes.GET, // key: "foo", // }, // { // effect: c.effect, // target: toRaw(obj), // type: TrackOpTypes.HAS, // key: "bar", // }, // { // effect: c.effect, // target: toRaw(obj), // type: TrackOpTypes.ITERATE, // key: ITERATE_KEY, // }, // ]); // }); // it("debug: onTrigger", () => { // let events: DebuggerEvent[] = []; // const onTrigger = vi.fn((e: DebuggerEvent) => { // events.push(e); // }); // const obj = reactive<{ foo?: number }>({ foo: 1 }); // const c = computed(() => obj.foo, { onTrigger }); // // computed won't trigger compute until accessed // c.value; // obj.foo!++; // expect(c.value).toBe(2); // expect(onTrigger).toHaveBeenCalledTimes(1); // expect(events[0]).toEqual({ // effect: c.effect, // target: toRaw(obj), // type: TriggerOpTypes.SET, // key: "foo", // oldValue: 1, // newValue: 2, // }); // delete obj.foo; // expect(c.value).toBeUndefined(); // expect(onTrigger).toHaveBeenCalledTimes(2); // expect(events[1]).toEqual({ // effect: c.effect, // target: toRaw(obj), // type: TriggerOpTypes.DELETE, // key: "foo", // oldValue: 2, // }); // }); });