Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions apps/web/src/modules/analyze/components/ScoreConversion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { act, render } from '@testing-library/react';
import ScoreConversion from './ScoreConversion';
import { chartUserSettingState } from '@modules/analyze/store';

jest.mock('next-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
jest.mock('public/images/analyze/number-100.svg', () => ({
__esModule: true,
default: () => null,
}));
jest.mock('public/images/analyze/number-1.svg', () => ({
__esModule: true,
default: () => null,
}));

describe('ScoreConversion', () => {
beforeEach(() => {
chartUserSettingState.onePointSys = false;
});

it('does not accumulate subscriptions across re-renders', async () => {
const onChange = jest.fn();
const { rerender } = render(
<ScoreConversion onePoint={false} onChange={onChange} />
);
rerender(<ScoreConversion onePoint={false} onChange={onChange} />);
rerender(<ScoreConversion onePoint={false} onChange={onChange} />);
rerender(<ScoreConversion onePoint={false} onChange={onChange} />);

// valtio 的订阅回调经微任务通知(subscribeKey 未传 notifyInSync),
// 需 flush 后断言。
await act(async () => {
chartUserSettingState.onePointSys = true;
});
expect(onChange).toHaveBeenCalledTimes(1);
});

it('does not call onChange when the store value equals the current one', async () => {
const onChange = jest.fn();
render(<ScoreConversion onePoint={false} onChange={onChange} />);
await act(async () => {
chartUserSettingState.onePointSys = false;
});
expect(onChange).not.toHaveBeenCalled();
});
});
24 changes: 15 additions & 9 deletions apps/web/src/modules/analyze/components/ScoreConversion.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import Svg100 from 'public/images/analyze/number-100.svg';
import Svg1 from 'public/images/analyze/number-1.svg';
import { useTranslation } from 'next-i18next';
Expand All @@ -11,15 +11,21 @@ const ScoreConversion: React.FC<{
onChange: (pre: boolean) => void;
}> = ({ onePoint, onChange }) => {
const { t } = useTranslation();
const unsubscribe = subscribeKey(
chartUserSettingState,
'onePointSys',
(v) => {
if (onePoint !== v) {
onChange(v);
// subscribeKey 必须在 effect 中订阅并在卸载/依赖变化时退订:在渲染体内调用
// 会随每次重渲染累积一个永不退订的订阅,导航栏切换一次设置会触发全部累积
// 回调(闭包陈旧、卸载后仍 setState)。
useEffect(() => {
const unsubscribe = subscribeKey(
chartUserSettingState,
'onePointSys',
(v) => {
if (onePoint !== v) {
onChange(v);
}
}
}
);
);
return unsubscribe;
}, [onePoint, onChange]);
return (
<Tooltip
arrow
Expand Down