hooks.test.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import * as React from 'react';
  6. import { describe, it, expect, afterEach } from 'vitest';
  7. import { render, cleanup } from '@testing-library/react';
  8. import { useReactiveState, useReadonlyReactiveState, Tracker, ReactiveState } from '../src';
  9. describe('hooks', () => {
  10. afterEach(() => cleanup());
  11. it('useReactiveState update more times', () => {
  12. let renderTimes = 0;
  13. const Comp = () => {
  14. renderTimes++;
  15. const value = useReactiveState({ a: 0, b: 0 });
  16. React.useEffect(() => {
  17. value.a = 1;
  18. value.a = 2;
  19. value.b = 1;
  20. value.b = 2;
  21. }, []);
  22. return (
  23. <div>
  24. {value.a} - {value.b}
  25. </div>
  26. );
  27. };
  28. const result = render(<Comp />);
  29. Tracker.flush();
  30. expect(renderTimes).toEqual(2); // batch update
  31. expect(result.asFragment().textContent).toEqual('2 - 2');
  32. });
  33. it('useReactiveState sub component', () => {
  34. let comp1RenderTimes = 0;
  35. let comp2RenderTimes = 0;
  36. const Comp1 = ({ value }: any) => {
  37. comp1RenderTimes++;
  38. React.useEffect(() => {
  39. value.a = 2;
  40. }, []);
  41. return <div>{value.a}</div>;
  42. };
  43. const Comp2 = () => {
  44. comp2RenderTimes++;
  45. const value = useReactiveState({ a: 0 });
  46. return <Comp1 value={value} />;
  47. };
  48. const result = render(<Comp2 />);
  49. function checkTimes(a: number, b: number) {
  50. expect(comp1RenderTimes).toEqual(a);
  51. expect(comp2RenderTimes).toEqual(b);
  52. }
  53. checkTimes(1, 1);
  54. Tracker.flush();
  55. checkTimes(2, 2);
  56. expect(result.asFragment().textContent).toEqual('2');
  57. });
  58. it('useReactiveState from outside', () => {
  59. let comp1RenderTimes = 0;
  60. let comp2RenderTimes = 0;
  61. const state = new ReactiveState({ a: 0, b: 0 });
  62. const Comp1 = ({ value }: any) => {
  63. comp1RenderTimes++;
  64. return <div>{comp1RenderTimes >= 3 ? '-' : value.a}</div>;
  65. };
  66. const Comp2 = () => {
  67. comp2RenderTimes++;
  68. const value = useReactiveState(state);
  69. return <Comp1 value={value} />;
  70. };
  71. const result = render(<Comp2 />);
  72. function checkTimes(a: number, b: number) {
  73. expect(comp1RenderTimes).toEqual(a);
  74. expect(comp2RenderTimes).toEqual(b);
  75. }
  76. checkTimes(1, 1);
  77. state.value.b = 1;
  78. Tracker.flush();
  79. checkTimes(1, 1); // b 没有依赖所有不更新
  80. state.value.a = 1;
  81. Tracker.flush();
  82. checkTimes(2, 2);
  83. state.value.a = 2;
  84. Tracker.flush();
  85. checkTimes(3, 3);
  86. expect(result.asFragment().textContent).toEqual('-');
  87. state.value.a = 3;
  88. Tracker.flush();
  89. checkTimes(3, 3); // a 不再依赖所以不更新
  90. });
  91. it('useReactiveState nested', () => {
  92. const state = new ReactiveState({ a: 0 });
  93. let comp1RenderTimes = 0;
  94. let comp2RenderTimes = 0;
  95. const Comp1 = () => {
  96. comp1RenderTimes++;
  97. const value = useReactiveState(state);
  98. React.useEffect(() => {
  99. value.a = 1;
  100. }, []);
  101. return <div>{value.a}</div>;
  102. };
  103. const Comp2 = () => {
  104. comp2RenderTimes++;
  105. const value = useReactiveState(state);
  106. React.useEffect(() => {
  107. value.a = 2;
  108. }, []);
  109. return (
  110. <div>
  111. <Comp1 /> - {value.a}
  112. </div>
  113. );
  114. };
  115. function checkTimes(a: number, b: number) {
  116. expect(comp1RenderTimes).toEqual(a);
  117. expect(comp2RenderTimes).toEqual(b);
  118. }
  119. const result = render(<Comp2 />);
  120. Tracker.flush();
  121. checkTimes(2, 2);
  122. expect(result.asFragment().textContent).toEqual('2 - 2');
  123. });
  124. it('useReadonlyReactiveState', () => {
  125. const state = new ReactiveState({ a: 0 });
  126. const Comp = () => {
  127. const v = useReadonlyReactiveState(state);
  128. expect(() => {
  129. (v as any).a = 3;
  130. }).toThrowError(/readonly/);
  131. return <div>{v.a}</div>;
  132. };
  133. render(<Comp />);
  134. });
  135. });