flowgram.ai/packages/common/reactive/__tests__/reactive-state.test.ts
chenjiawei.inizio cbefaa54fb
chore: add license header (#432)
* chore: add license-header

* chore: add precommit

* chore: add license header

* fix: only js & shell style
2025-07-01 11:53:02 +00:00

135 lines
4.4 KiB
TypeScript

/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/
import { describe, it, expect } from 'vitest';
import { ReactiveState, Tracker } from '../src';
describe('reactive-state', () => {
it('base', () => {
const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
const { value } = state;
let autorunTimes = -1;
const compute = Tracker.autorun<number>(() => {
autorunTimes++;
return value.a;
});
expect(state.hasDependents()).toEqual(true);
expect(autorunTimes).toEqual(0);
expect(compute.result).toEqual(0);
state.value.a = 1;
expect(compute.result).toEqual(0);
expect(autorunTimes).toEqual(0);
Tracker.flush();
expect(compute.result).toEqual(1);
expect(autorunTimes).toEqual(1);
Tracker.flush();
// Still 1!
expect(compute.result).toEqual(1);
expect(autorunTimes).toEqual(1);
state.value.a = 1;
Tracker.flush();
expect(compute.result).toEqual(1);
expect(autorunTimes).toEqual(1);
state.value.b = 1;
Tracker.flush();
expect(compute.result).toEqual(1);
expect(autorunTimes).toEqual(1);
});
it('keys', () => {
const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
expect(state.keys()).toEqual(['a', 'b']);
expect(Object.keys(state.value)).toEqual(['a', 'b']);
expect(Object.keys(state.readonlyValue)).toEqual(['a', 'b']);
});
it('hasDependents', () => {
const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
expect(state.hasDependents()).toEqual(false);
const compute = Tracker.autorun<number>(() => state.value.a);
expect(state.hasDependents()).toEqual(true);
compute.stop();
expect(state.hasDependents()).toEqual(false);
});
it('set all value', () => {
const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
const compute = Tracker.autorun<number>(() => state.value.a);
state.value = { a: 1, b: 1 };
Tracker.flush();
expect(compute.result).toEqual(1);
});
it('dict state iterator (use Proxy)', () => {
const { value } = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
let autorunTimes = -1;
const compute = Tracker.autorun<{ a: number; b: number }>(() => {
autorunTimes++;
const result = {};
for (let key in value) {
result[key] = value[key];
}
return result as any;
});
expect(autorunTimes).toEqual(0);
expect(compute.result).toEqual({ a: 0, b: 0 });
value.a = 1;
value.b = 1;
Tracker.flush();
expect(autorunTimes).toEqual(1);
expect(compute.result).toEqual({ a: 1, b: 1 });
});
it('dict state iterator (use defineProperty)', () => {
global.__ignoreProxy = true;
const { value } = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
let autorunTimes = -1;
const compute = Tracker.autorun<{ a: number; b: number }>(() => {
autorunTimes++;
const result = {};
for (let key in value) {
result[key] = value[key];
}
return result as any;
});
expect(Object.keys(value)).toEqual(['a', 'b']);
expect(autorunTimes).toEqual(0);
expect(compute.result).toEqual({ a: 0, b: 0 });
value.a = 1;
value.b = 1;
Tracker.flush();
expect(autorunTimes).toEqual(1);
expect(compute.result).toEqual({ a: 1, b: 1 });
global.__ignoreProxy = false;
});
it('set unknown field', () => {
const { value } = new ReactiveState<Record<string, any>>({});
let runTimes = 0;
const compute = Tracker.autorun(() => {
runTimes++;
return value.a;
});
value.a = 'new field';
Tracker.flush();
expect(runTimes).toEqual(2);
expect(compute.result).toEqual('new field');
expect(Object.keys(value)).toEqual(['a']);
delete value.a;
expect(Object.keys(value)).toEqual([]);
});
it('readonly value (use Proxy)', () => {
const originState = new ReactiveState({ a: 0, b: 0 });
const readonlyValue = originState.readonlyValue;
expect(() => {
(readonlyValue as any).a = 1;
}).toThrow(/readonly field/);
});
it('readonly value (use define property)', () => {
global.__ignoreProxy = true;
const originState = new ReactiveState({ a: 0, b: 0 });
const readonlyValue = originState.readonlyValue;
expect(() => {
(readonlyValue as any).a = 1;
}).toThrow(/readonly field/);
global.__ignoreProxy = false;
});
});