fix(actions): prevent state mutation and retain newest actions when limit reached#34160
fix(actions): prevent state mutation and retain newest actions when limit reached#34160shanliuling wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughA comprehensive test suite for ActionLogger's addAction logic has been added, and the implementation has been refactored to use immutable update patterns, replacing direct mutations of action counts and arrays with new object creation and trimming operations. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can generate a title for your PR based on the changes with custom instructions.Set the |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
code/core/src/actions/containers/ActionLogger/index.test.ts (1)
15-26: Testing duplicated logic instead of the actual implementation.The test duplicates the
addActionlogic locally rather than testing the actual implementation inActionLogger. This creates a risk that:
- Tests pass even if the real implementation has bugs
- The test's
addActionand productionaddActioncould diverge over timeConsider extracting the pure
addActionlogic from the component into a separate, exportable utility function that can be imported and tested directly. This aligns better with testing real behavior.♻️ Suggested approach
// In a separate file, e.g., actionUtils.ts export function addAction(prevActions: ActionDisplay[], action: ActionDisplay): ActionDisplay[] { const previous = prevActions.length ? prevActions[prevActions.length - 1] : null; // ... rest of implementation } // In the test file import { addAction } from './actionUtils';Then in the component:
import { addAction } from './actionUtils'; // Use it in useCallbackBased on learnings: "Export functions that need direct tests and test real behavior, not just syntax patterns"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/core/src/actions/containers/ActionLogger/index.test.ts` around lines 15 - 26, The test currently reimplements the addAction logic locally instead of testing the real implementation in ActionLogger; extract the pure addAction logic from the ActionLogger component into a new exported utility (e.g., actionUtils.ts) that exports function addAction(prevActions: ActionDisplay[], action: ActionDisplay): ActionDisplay[], update the ActionLogger to import and use this exported addAction (for example inside useCallback), and change the test to import addAction from the new utility and assert its behavior so the test covers the actual implementation rather than a duplicated copy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@code/core/src/actions/containers/ActionLogger/index.test.ts`:
- Around line 158-173: The test's assertions contradict the actual behavior of
addAction/createAction: with limit = 2 the final result should contain two
distinct actions (args ['1','2']) with counts [1,2]. Fix by either (A) keeping
limit = 2 and update expectations to expect length 2, args order ['1','2'],
result[0].count === 1 and result[1].count === 2, or (B) change the limit to 1 if
the intent was to push out the older action and then assert length 1, args
['2'], and count 2; adjust the assertions around createAction and addAction
accordingly.
---
Nitpick comments:
In `@code/core/src/actions/containers/ActionLogger/index.test.ts`:
- Around line 15-26: The test currently reimplements the addAction logic locally
instead of testing the real implementation in ActionLogger; extract the pure
addAction logic from the ActionLogger component into a new exported utility
(e.g., actionUtils.ts) that exports function addAction(prevActions:
ActionDisplay[], action: ActionDisplay): ActionDisplay[], update the
ActionLogger to import and use this exported addAction (for example inside
useCallback), and change the test to import addAction from the new utility and
assert its behavior so the test covers the actual implementation rather than a
duplicated copy.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b7b60efc-96d4-4e00-ab56-ab1fcd19a157
📒 Files selected for processing (2)
code/core/src/actions/containers/ActionLogger/index.test.tscode/core/src/actions/containers/ActionLogger/index.tsx
| it('should still apply limit when incrementing count', () => { | ||
| const limit = 2; | ||
| const action1 = createAction('click', ['1'], { limit }); | ||
| const action2 = createAction('click', ['2'], { limit }); | ||
| const action3 = createAction('click', ['2'], { limit }); // Same as action2 | ||
|
|
||
| let result: ActionDisplay[] = []; | ||
| result = addAction(result, action1); | ||
| result = addAction(result, action2); | ||
| result = addAction(result, action3); | ||
|
|
||
| expect(result).toHaveLength(2); | ||
| // Should keep ['click 2' (count: 2)] since action1 was pushed out | ||
| expect(result.map((a) => a.data.args[0])).toEqual(['2']); | ||
| expect(result[0].count).toBe(2); | ||
| }); |
There was a problem hiding this comment.
Test expectations appear incorrect for the limit scenario.
Tracing through the test logic:
addAction([], action1)→[{ args: ['1'], count: 1 }]addAction(result, action2)→[{ args: ['1'], count: 1 }, { args: ['2'], count: 1 }]addAction(result, action3)(same data as action2) → updates last element's count to 2, slices to last 2 elements →[{ args: ['1'], count: 1 }, { args: ['2'], count: 2 }]
With limit: 2, no action gets pushed out since there are only 2 distinct actions. The expectations at lines 171-172 appear inconsistent:
- Line 169 expects length 2
- Line 171 expects
map(...args[0])to equal['2'](length 1) - Line 172 expects
result[0].countto be 2, butresult[0]would be the action withargs: ['1']havingcount: 1
🐛 Suggested fix for test expectations
expect(result).toHaveLength(2);
- // Should keep ['click 2' (count: 2)] since action1 was pushed out
- expect(result.map((a) => a.data.args[0])).toEqual(['2']);
- expect(result[0].count).toBe(2);
+ // Both actions remain since limit is 2
+ expect(result.map((a) => a.data.args[0])).toEqual(['1', '2']);
+ expect(result[0].count).toBe(1);
+ expect(result[1].count).toBe(2);Or if the intent was to test that older actions get pushed out when incrementing, use limit: 1:
- const limit = 2;
+ const limit = 1;
const action1 = createAction('click', ['1'], { limit });
const action2 = createAction('click', ['2'], { limit });
const action3 = createAction('click', ['2'], { limit }); // Same as action2
let result: ActionDisplay[] = [];
result = addAction(result, action1);
result = addAction(result, action2);
result = addAction(result, action3);
- expect(result).toHaveLength(2);
- // Should keep ['click 2' (count: 2)] since action1 was pushed out
- expect(result.map((a) => a.data.args[0])).toEqual(['2']);
- expect(result[0].count).toBe(2);
+ expect(result).toHaveLength(1);
+ expect(result[0].data.args[0]).toBe('2');
+ expect(result[0].count).toBe(2);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('should still apply limit when incrementing count', () => { | |
| const limit = 2; | |
| const action1 = createAction('click', ['1'], { limit }); | |
| const action2 = createAction('click', ['2'], { limit }); | |
| const action3 = createAction('click', ['2'], { limit }); // Same as action2 | |
| let result: ActionDisplay[] = []; | |
| result = addAction(result, action1); | |
| result = addAction(result, action2); | |
| result = addAction(result, action3); | |
| expect(result).toHaveLength(2); | |
| // Should keep ['click 2' (count: 2)] since action1 was pushed out | |
| expect(result.map((a) => a.data.args[0])).toEqual(['2']); | |
| expect(result[0].count).toBe(2); | |
| }); | |
| it('should still apply limit when incrementing count', () => { | |
| const limit = 2; | |
| const action1 = createAction('click', ['1'], { limit }); | |
| const action2 = createAction('click', ['2'], { limit }); | |
| const action3 = createAction('click', ['2'], { limit }); // Same as action2 | |
| let result: ActionDisplay[] = []; | |
| result = addAction(result, action1); | |
| result = addAction(result, action2); | |
| result = addAction(result, action3); | |
| expect(result).toHaveLength(2); | |
| // Both actions remain since limit is 2 | |
| expect(result.map((a) => a.data.args[0])).toEqual(['1', '2']); | |
| expect(result[0].count).toBe(1); | |
| expect(result[1].count).toBe(2); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@code/core/src/actions/containers/ActionLogger/index.test.ts` around lines 158
- 173, The test's assertions contradict the actual behavior of
addAction/createAction: with limit = 2 the final result should contain two
distinct actions (args ['1','2']) with counts [1,2]. Fix by either (A) keeping
limit = 2 and update expectations to expect length 2, args order ['1','2'],
result[0].count === 1 and result[1].count === 2, or (B) change the limit to 1 if
the intent was to push out the older action and then assert length 1, args
['2'], and count 2; adjust the assertions around createAction and addAction
accordingly.
- Extract pure addAction logic from ActionLogger component to actionUtils.ts - Update test to import and test the real implementation instead of duplicating logic - Add proper JSDoc documentation for the exported function Fixes CodeRabbit review feedback
a901f49 to
d0a817f
Compare
What is the problem?
The
ActionLoggercomponent has two issues:State mutation: The
addActionfunction mutates objects directly in React state, which violates React's immutability principle and can cause subtle bugs:previous.count++mutates the existing action objectaction.count = 1mutates the incoming action objectWrong slice direction:
slice(0, limit)keeps the oldest actions and discards the newest when at capacity. Users typically expect to see the most recent action logs.How did I fix it?
Immutability fix: Create new objects instead of mutating:
Slice direction fix: Use
slice(-limit)to keep the newest actions:How was this tested?
Closes #34052
Summary by CodeRabbit
Tests
Bug Fixes