reactive-state.test.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { describe, it, expect } from 'vitest';
  6. import { ReactiveState, Tracker } from '../src';
  7. describe('reactive-state', () => {
  8. it('base', () => {
  9. const state = new ReactiveState({ a: 0, b: 0 });
  10. const { value } = state;
  11. let autorunTimes = -1;
  12. const compute = Tracker.autorun<number>(() => {
  13. autorunTimes++;
  14. return value.a;
  15. });
  16. expect(state.hasDependents()).toEqual(true);
  17. expect(autorunTimes).toEqual(0);
  18. expect(compute.result).toEqual(0);
  19. state.value.a = 1;
  20. expect(compute.result).toEqual(0);
  21. expect(autorunTimes).toEqual(0);
  22. Tracker.flush();
  23. expect(compute.result).toEqual(1);
  24. expect(autorunTimes).toEqual(1);
  25. Tracker.flush();
  26. // Still 1!
  27. expect(compute.result).toEqual(1);
  28. expect(autorunTimes).toEqual(1);
  29. state.value.a = 1;
  30. Tracker.flush();
  31. expect(compute.result).toEqual(1);
  32. expect(autorunTimes).toEqual(1);
  33. state.value.b = 1;
  34. Tracker.flush();
  35. expect(compute.result).toEqual(1);
  36. expect(autorunTimes).toEqual(1);
  37. });
  38. it('keys', () => {
  39. const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
  40. expect(state.keys()).toEqual(['a', 'b']);
  41. expect(Object.keys(state.value)).toEqual(['a', 'b']);
  42. expect(Object.keys(state.readonlyValue)).toEqual(['a', 'b']);
  43. });
  44. it('hasDependents', () => {
  45. const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
  46. expect(state.hasDependents()).toEqual(false);
  47. const compute = Tracker.autorun<number>(() => state.value.a);
  48. expect(state.hasDependents()).toEqual(true);
  49. compute.stop();
  50. expect(state.hasDependents()).toEqual(false);
  51. });
  52. it('set all value', () => {
  53. const state = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
  54. const compute = Tracker.autorun<number>(() => state.value.a);
  55. state.value = { a: 1, b: 1 };
  56. Tracker.flush();
  57. expect(compute.result).toEqual(1);
  58. });
  59. it('dict state iterator (use Proxy)', () => {
  60. const { value } = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
  61. let autorunTimes = -1;
  62. const compute = Tracker.autorun<{ a: number; b: number }>(() => {
  63. autorunTimes++;
  64. const result = {};
  65. for (let key in value) {
  66. result[key] = value[key];
  67. }
  68. return result as any;
  69. });
  70. expect(autorunTimes).toEqual(0);
  71. expect(compute.result).toEqual({ a: 0, b: 0 });
  72. value.a = 1;
  73. value.b = 1;
  74. Tracker.flush();
  75. expect(autorunTimes).toEqual(1);
  76. expect(compute.result).toEqual({ a: 1, b: 1 });
  77. });
  78. it('dict state iterator (use defineProperty)', () => {
  79. global.__ignoreProxy = true;
  80. const { value } = new ReactiveState<{ a: number; b: number }>({ a: 0, b: 0 });
  81. let autorunTimes = -1;
  82. const compute = Tracker.autorun<{ a: number; b: number }>(() => {
  83. autorunTimes++;
  84. const result = {};
  85. for (let key in value) {
  86. result[key] = value[key];
  87. }
  88. return result as any;
  89. });
  90. expect(Object.keys(value)).toEqual(['a', 'b']);
  91. expect(autorunTimes).toEqual(0);
  92. expect(compute.result).toEqual({ a: 0, b: 0 });
  93. value.a = 1;
  94. value.b = 1;
  95. Tracker.flush();
  96. expect(autorunTimes).toEqual(1);
  97. expect(compute.result).toEqual({ a: 1, b: 1 });
  98. global.__ignoreProxy = false;
  99. });
  100. it('set unknown field', () => {
  101. const { value } = new ReactiveState<Record<string, any>>({});
  102. let runTimes = 0;
  103. const compute = Tracker.autorun(() => {
  104. runTimes++;
  105. return value.a;
  106. });
  107. value.a = 'new field';
  108. Tracker.flush();
  109. expect(runTimes).toEqual(2);
  110. expect(compute.result).toEqual('new field');
  111. expect(Object.keys(value)).toEqual(['a']);
  112. delete value.a;
  113. expect(Object.keys(value)).toEqual([]);
  114. });
  115. it('readonly value (use Proxy)', () => {
  116. const originState = new ReactiveState({ a: 0, b: 0 });
  117. const readonlyValue = originState.readonlyValue;
  118. expect(() => {
  119. (readonlyValue as any).a = 1;
  120. }).toThrow(/readonly field/);
  121. });
  122. it('readonly value (use define property)', () => {
  123. global.__ignoreProxy = true;
  124. const originState = new ReactiveState({ a: 0, b: 0 });
  125. const readonlyValue = originState.readonlyValue;
  126. expect(() => {
  127. (readonlyValue as any).a = 1;
  128. }).toThrow(/readonly field/);
  129. global.__ignoreProxy = false;
  130. });
  131. });